1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2002-2014 by the Claws Mail Team and Hiroyuki Yamamoto
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  *
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #include "claws-features.h"
23 #endif
24 
25 #include <glib.h>
26 #include <glib/gi18n.h>
27 #include <ctype.h>
28 #include <string.h>
29 #include <stdlib.h>
30 #include <errno.h>
31 
32 #ifdef USE_PTHREAD
33 #include <pthread.h>
34 #endif
35 
36 #include "defs.h"
37 #include "utils.h"
38 #include "procheader.h"
39 #include "matcher.h"
40 #include "matcher_parser.h"
41 #include "prefs_gtk.h"
42 #include "addr_compl.h"
43 #include "codeconv.h"
44 #include "quoted-printable.h"
45 #include "claws.h"
46 #include <ctype.h>
47 #include "prefs_common.h"
48 #include "log.h"
49 #include "tags.h"
50 #include "folder_item_prefs.h"
51 #include "procmsg.h"
52 #include "file-utils.h"
53 
54 /*!
55  *\brief	Keyword lookup element
56  */
57 struct _MatchParser {
58 	gint id;		/*!< keyword id */
59 	gchar *str;		/*!< keyword */
60 };
61 typedef struct _MatchParser MatchParser;
62 
63 /*!
64  *\brief	Table with strings and ids used by the lexer and
65  *		the parser. New keywords can be added here.
66  */
67 static const MatchParser matchparser_tab[] = {
68 	/* msginfo flags */
69 	{MATCHCRITERIA_ALL, "all"},
70 	{MATCHCRITERIA_UNREAD, "unread"},
71 	{MATCHCRITERIA_NOT_UNREAD, "~unread"},
72 	{MATCHCRITERIA_NEW, "new"},
73 	{MATCHCRITERIA_NOT_NEW, "~new"},
74 	{MATCHCRITERIA_MARKED, "marked"},
75 	{MATCHCRITERIA_NOT_MARKED, "~marked"},
76 	{MATCHCRITERIA_DELETED, "deleted"},
77 	{MATCHCRITERIA_NOT_DELETED, "~deleted"},
78 	{MATCHCRITERIA_REPLIED, "replied"},
79 	{MATCHCRITERIA_NOT_REPLIED, "~replied"},
80 	{MATCHCRITERIA_FORWARDED, "forwarded"},
81 	{MATCHCRITERIA_NOT_FORWARDED, "~forwarded"},
82 	{MATCHCRITERIA_LOCKED, "locked"},
83 	{MATCHCRITERIA_NOT_LOCKED, "~locked"},
84 	{MATCHCRITERIA_COLORLABEL, "colorlabel"},
85 	{MATCHCRITERIA_NOT_COLORLABEL, "~colorlabel"},
86 	{MATCHCRITERIA_IGNORE_THREAD, "ignore_thread"},
87 	{MATCHCRITERIA_NOT_IGNORE_THREAD, "~ignore_thread"},
88 	{MATCHCRITERIA_WATCH_THREAD, "watch_thread"},
89 	{MATCHCRITERIA_NOT_WATCH_THREAD, "~watch_thread"},
90 	{MATCHCRITERIA_SPAM, "spam"},
91 	{MATCHCRITERIA_NOT_SPAM, "~spam"},
92 	{MATCHCRITERIA_HAS_ATTACHMENT, "has_attachment"},
93 	{MATCHCRITERIA_HAS_NO_ATTACHMENT, "~has_attachment"},
94 	{MATCHCRITERIA_SIGNED, "signed"},
95 	{MATCHCRITERIA_NOT_SIGNED, "~signed"},
96 
97 	/* msginfo headers */
98 	{MATCHCRITERIA_SUBJECT, "subject"},
99 	{MATCHCRITERIA_NOT_SUBJECT, "~subject"},
100 	{MATCHCRITERIA_FROM, "from"},
101 	{MATCHCRITERIA_NOT_FROM, "~from"},
102 	{MATCHCRITERIA_TO, "to"},
103 	{MATCHCRITERIA_NOT_TO, "~to"},
104 	{MATCHCRITERIA_CC, "cc"},
105 	{MATCHCRITERIA_NOT_CC, "~cc"},
106 	{MATCHCRITERIA_TO_OR_CC, "to_or_cc"},
107 	{MATCHCRITERIA_NOT_TO_AND_NOT_CC, "~to_or_cc"},
108 	{MATCHCRITERIA_TAG, "tag"},
109 	{MATCHCRITERIA_NOT_TAG, "~tag"},
110 	{MATCHCRITERIA_TAGGED, "tagged"},
111 	{MATCHCRITERIA_NOT_TAGGED, "~tagged"},
112 	{MATCHCRITERIA_AGE_GREATER, "age_greater"},
113 	{MATCHCRITERIA_AGE_LOWER, "age_lower"},
114 	{MATCHCRITERIA_AGE_GREATER_HOURS, "age_greater_hours"},
115 	{MATCHCRITERIA_AGE_LOWER_HOURS, "age_lower_hours"},
116 	{MATCHCRITERIA_DATE_AFTER, "date_after"},
117 	{MATCHCRITERIA_DATE_BEFORE, "date_before"},
118 	{MATCHCRITERIA_NEWSGROUPS, "newsgroups"},
119 	{MATCHCRITERIA_NOT_NEWSGROUPS, "~newsgroups"},
120 	{MATCHCRITERIA_MESSAGEID, "messageid"},
121 	{MATCHCRITERIA_NOT_MESSAGEID, "~messageid"},
122 	{MATCHCRITERIA_INREPLYTO, "inreplyto"},
123 	{MATCHCRITERIA_NOT_INREPLYTO, "~inreplyto"},
124 	{MATCHCRITERIA_REFERENCES, "references"},
125 	{MATCHCRITERIA_NOT_REFERENCES, "~references"},
126 	{MATCHCRITERIA_SCORE_GREATER, "score_greater"},
127 	{MATCHCRITERIA_SCORE_LOWER, "score_lower"},
128 	{MATCHCRITERIA_SCORE_EQUAL, "score_equal"},
129 	{MATCHCRITERIA_PARTIAL, "partial"},
130 	{MATCHCRITERIA_NOT_PARTIAL, "~partial"},
131 	{MATCHCRITERIA_FOUND_IN_ADDRESSBOOK, "found_in_addressbook"},
132 	{MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK, "~found_in_addressbook"},
133 
134 	{MATCHCRITERIA_SIZE_GREATER, "size_greater"},
135 	{MATCHCRITERIA_SIZE_SMALLER, "size_smaller"},
136 	{MATCHCRITERIA_SIZE_EQUAL,   "size_equal"},
137 
138 	/* content have to be read */
139 	{MATCHCRITERIA_HEADER, "header"},
140 	{MATCHCRITERIA_NOT_HEADER, "~header"},
141 	{MATCHCRITERIA_HEADERS_PART, "headers_part"},
142 	{MATCHCRITERIA_NOT_HEADERS_PART, "~headers_part"},
143 	{MATCHCRITERIA_HEADERS_CONT, "headers_cont"},
144 	{MATCHCRITERIA_NOT_HEADERS_CONT, "~headers_cont"},
145 	{MATCHCRITERIA_MESSAGE, "message"},
146 	{MATCHCRITERIA_NOT_MESSAGE, "~message"},
147 	{MATCHCRITERIA_BODY_PART, "body_part"},
148 	{MATCHCRITERIA_NOT_BODY_PART, "~body_part"},
149 	{MATCHCRITERIA_TEST, "test"},
150 	{MATCHCRITERIA_NOT_TEST, "~test"},
151 
152 	/* match type */
153 	{MATCHTYPE_MATCHCASE, "matchcase"},
154 	{MATCHTYPE_MATCH, "match"},
155 	{MATCHTYPE_REGEXPCASE, "regexpcase"},
156 	{MATCHTYPE_REGEXP, "regexp"},
157 
158 	/* actions */
159 	{MATCHACTION_SCORE, "score"},    /* for backward compatibility */
160 	{MATCHACTION_MOVE, "move"},
161 	{MATCHACTION_COPY, "copy"},
162 	{MATCHACTION_DELETE, "delete"},
163 	{MATCHACTION_MARK, "mark"},
164 	{MATCHACTION_UNMARK, "unmark"},
165 	{MATCHACTION_LOCK, "lock"},
166 	{MATCHACTION_UNLOCK, "unlock"},
167 	{MATCHACTION_MARK_AS_READ, "mark_as_read"},
168 	{MATCHACTION_MARK_AS_UNREAD, "mark_as_unread"},
169 	{MATCHACTION_MARK_AS_SPAM, "mark_as_spam"},
170 	{MATCHACTION_MARK_AS_HAM, "mark_as_ham"},
171 	{MATCHACTION_FORWARD, "forward"},
172 	{MATCHACTION_FORWARD_AS_ATTACHMENT, "forward_as_attachment"},
173 	{MATCHACTION_EXECUTE, "execute"},
174 	{MATCHACTION_COLOR, "color"},
175 	{MATCHACTION_REDIRECT, "redirect"},
176 	{MATCHACTION_CHANGE_SCORE, "change_score"},
177 	{MATCHACTION_SET_SCORE, "set_score"},
178 	{MATCHACTION_STOP, "stop"},
179 	{MATCHACTION_HIDE, "hide"},
180 	{MATCHACTION_IGNORE, "ignore"},
181 	{MATCHACTION_WATCH, "watch"},
182 	{MATCHACTION_ADD_TO_ADDRESSBOOK, "add_to_addressbook"},
183 	{MATCHACTION_SET_TAG, "set_tag"},
184 	{MATCHACTION_UNSET_TAG, "unset_tag"},
185 	{MATCHACTION_CLEAR_TAGS, "clear_tags"},
186 };
187 
188 enum {
189 	MATCH_ANY = 0,
190 	MATCH_ALL = 1,
191 	MATCH_ONE = 2
192 };
193 
194 enum {
195 	CONTEXT_SUBJECT,
196 	CONTEXT_FROM,
197 	CONTEXT_TO,
198 	CONTEXT_CC,
199 	CONTEXT_NEWSGROUPS,
200 	CONTEXT_MESSAGEID,
201 	CONTEXT_IN_REPLY_TO,
202 	CONTEXT_REFERENCES,
203 	CONTEXT_HEADER,
204 	CONTEXT_HEADER_LINE,
205 	CONTEXT_BODY_LINE,
206 	CONTEXT_TAG,
207 	N_CONTEXT_STRS
208 };
209 
210 static gchar *context_str[N_CONTEXT_STRS];
211 
matcher_init(void)212 void matcher_init(void)
213 {
214 	if (context_str[CONTEXT_SUBJECT] != NULL)
215 		return;
216 
217 	context_str[CONTEXT_SUBJECT] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Subject:"));
218 	context_str[CONTEXT_FROM] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("From:"));
219 	context_str[CONTEXT_TO] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("To:"));
220 	context_str[CONTEXT_CC] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Cc:"));
221 	context_str[CONTEXT_NEWSGROUPS] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Newsgroups:"));
222 	context_str[CONTEXT_MESSAGEID] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Message-ID:"));
223 	context_str[CONTEXT_IN_REPLY_TO] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("In-Reply-To:"));
224 	context_str[CONTEXT_REFERENCES] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("References:"));
225 	context_str[CONTEXT_HEADER] = g_strdup(_("header"));
226 	context_str[CONTEXT_HEADER_LINE] = g_strdup(_("header line"));
227 	context_str[CONTEXT_BODY_LINE] = g_strdup(_("body line"));
228 	context_str[CONTEXT_TAG]  = g_strdup(_("tag"));
229 }
230 
matcher_done(void)231 void matcher_done(void)
232 {
233 	int i;
234 	for (i = 0; i < N_CONTEXT_STRS; i++) {
235 		g_free(context_str[i]);
236 		context_str[i] = NULL;
237 	}
238 }
239 
240 extern gboolean debug_filtering_session;
241 
242 /*!
243  *\brief	Look up table with keywords defined in \sa matchparser_tab
244  */
245 static GHashTable *matchparser_hashtab;
246 
247 /*!
248  *\brief	Translate keyword id to keyword string
249  *
250  *\param	id Id of keyword
251  *
252  *\return	const gchar * Keyword
253  */
get_matchparser_tab_str(gint id)254 const gchar *get_matchparser_tab_str(gint id)
255 {
256 	gint i;
257 
258 	for (i = 0; i < sizeof matchparser_tab / sizeof matchparser_tab[0]; i++) {
259 		if (matchparser_tab[i].id == id)
260 			return matchparser_tab[i].str;
261 	}
262 	return NULL;
263 }
264 
265 /*!
266  *\brief	Create keyword lookup table
267  */
create_matchparser_hashtab(void)268 static void create_matchparser_hashtab(void)
269 {
270 	int i;
271 
272 	if (matchparser_hashtab) return;
273 	matchparser_hashtab = g_hash_table_new(g_str_hash, g_str_equal);
274 	for (i = 0; i < sizeof matchparser_tab / sizeof matchparser_tab[0]; i++)
275 		g_hash_table_insert(matchparser_hashtab,
276 				    matchparser_tab[i].str,
277 				    (gpointer) &matchparser_tab[i]);
278 }
279 
280 /*!
281  *\brief	Return a keyword id from a keyword string
282  *
283  *\param	str Keyword string
284  *
285  *\return	gint Keyword id
286  */
get_matchparser_tab_id(const gchar * str)287 gint get_matchparser_tab_id(const gchar *str)
288 {
289 	MatchParser *res;
290 
291 	if (NULL != (res = g_hash_table_lookup(matchparser_hashtab, str))) {
292 		return res->id;
293 	} else
294 		return -1;
295 }
296 
297 /* **************** data structure allocation **************** */
298 
299 /*!
300  *\brief	Allocate a structure for a filtering / scoring
301  *		"condition" (a matcher structure)
302  *
303  *\param	criteria Criteria ID (MATCHCRITERIA_XXXX)
304  *\param	header Header string (if criteria is MATCHCRITERIA_HEADER
305 			or MATCHCRITERIA_FOUND_IN_ADDRESSBOOK)
306  *\param	matchtype Type of action (MATCHTYPE_XXX)
307  *\param	expr String value or expression to check
308  *\param	value Integer value to check
309  *
310  *\return	MatcherProp * Pointer to newly allocated structure
311  */
matcherprop_new(gint criteria,const gchar * header,gint matchtype,const gchar * expr,int value)312 MatcherProp *matcherprop_new(gint criteria, const gchar *header,
313 			      gint matchtype, const gchar *expr,
314 			      int value)
315 {
316 	MatcherProp *prop;
317 
318  	prop = g_new0(MatcherProp, 1);
319 	prop->criteria = criteria;
320 	prop->header = header != NULL ? g_strdup(header) : NULL;
321 
322 	prop->expr = expr != NULL ? g_strdup(expr) : NULL;
323 
324 	prop->matchtype = matchtype;
325 #ifndef G_OS_WIN32
326 	prop->preg = NULL;
327 #endif
328 	prop->casefold_expr = NULL;
329 	prop->value = value;
330 	prop->error = 0;
331 
332 	return prop;
333 }
334 
335 /*!
336  *\brief	Free a matcher structure
337  *
338  *\param	prop Pointer to matcher structure allocated with
339  *		#matcherprop_new
340  */
matcherprop_free(MatcherProp * prop)341 void matcherprop_free(MatcherProp *prop)
342 {
343 	g_free(prop->expr);
344 	g_free(prop->header);
345 #ifndef G_OS_WIN32
346 	if (prop->preg != NULL) {
347 		regfree(prop->preg);
348 		g_free(prop->preg);
349 	}
350 	g_free(prop->casefold_expr);
351 #endif
352 	g_free(prop);
353 }
354 
355 /*!
356  *\brief	Copy a matcher structure
357  *
358  *\param	src Matcher structure to copy
359  *
360  *\return	MatcherProp * Pointer to newly allocated matcher structure
361  */
matcherprop_copy(const MatcherProp * src)362 MatcherProp *matcherprop_copy(const MatcherProp *src)
363 {
364 	MatcherProp *prop = g_new0(MatcherProp, 1);
365 
366 	prop->criteria = src->criteria;
367 	prop->header = src->header ? g_strdup(src->header) : NULL;
368 	prop->expr = src->expr ? g_strdup(src->expr) : NULL;
369 	prop->matchtype = src->matchtype;
370 
371 #ifndef G_OS_WIN32
372 	prop->preg = NULL; /* will be re-evaluated */
373 #endif
374 	prop->casefold_expr = src->casefold_expr ? g_strdup(src->casefold_expr) : NULL;
375 	prop->value = src->value;
376 	prop->error = src->error;
377 	return prop;
378 }
379 
380 /* ************** match ******************************/
381 
match_with_addresses_in_addressbook(MatcherProp * prop,GSList * address_list,gint type,gchar * folderpath,gint match)382 static gboolean match_with_addresses_in_addressbook
383 	(MatcherProp *prop, GSList *address_list, gint type,
384 	 gchar* folderpath, gint match)
385 {
386 	GSList *walk = NULL;
387 	gboolean found = FALSE;
388 	gchar *path = NULL;
389 
390 	cm_return_val_if_fail(address_list != NULL, FALSE);
391 
392 	debug_print("match_with_addresses_in_addressbook(%d, %s)\n",
393 				g_slist_length(address_list), folderpath?folderpath:"(null)");
394 
395 	if (folderpath == NULL ||
396 		strcasecmp(folderpath, "Any") == 0 ||
397 		*folderpath == '\0')
398 		path = NULL;
399 	else
400 		path = folderpath;
401 
402 	start_address_completion(path);
403 
404 	for (walk = address_list; walk != NULL; walk = walk->next) {
405 		/* exact matching of email address */
406 		guint num_addr = complete_address(walk->data);
407 		found = FALSE;
408 		if (num_addr > 1) {
409 			/* skip first item (this is the search string itself) */
410 			int i = 1;
411 			for (; i < num_addr && !found; i++) {
412 				gchar *addr = get_complete_address(i);
413 				extract_address(addr);
414 				if (strcasecmp(addr, walk->data) == 0) {
415 					found = TRUE;
416 
417 					/* debug output */
418 					if (debug_filtering_session
419 							&& prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
420 						log_print(LOG_DEBUG_FILTERING,
421 								"address [ %s ] matches\n",
422 								(gchar *)walk->data);
423 					}
424 				}
425 				g_free(addr);
426 			}
427 		}
428 		/* debug output */
429 		if (debug_filtering_session
430 				&& prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH
431 				&& !found) {
432 			log_print(LOG_DEBUG_FILTERING,
433 					"address [ %s ] does NOT match\n",
434 					(gchar *)walk->data);
435 		}
436 		g_free(walk->data);
437 
438 		if (match == MATCH_ALL) {
439 			/* if matching all addresses, stop if one doesn't match */
440 			if (!found) {
441 				/* debug output */
442 				if (debug_filtering_session
443 						&& prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
444 					log_print(LOG_DEBUG_FILTERING,
445 							"not all address match (matching all)\n");
446 				}
447 				break;
448 			}
449 		} else if (match == MATCH_ANY) {
450 			/* if matching any address, stop if one does match */
451 			if (found) {
452 				/* debug output */
453 				if (debug_filtering_session
454 						&& prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
455 					log_print(LOG_DEBUG_FILTERING,
456 							"at least one address matches (matching any)\n");
457 				}
458 				break;
459 			}
460 		}
461 		/* MATCH_ONE: there should be only one loop iteration */
462 	}
463 
464 	end_address_completion();
465 
466 	return found;
467 }
468 
469 /*!
470  *\brief	Find out if a string matches a condition
471  *
472  *\param	prop Matcher structure
473  *\param	str String to check
474  *
475  *\return	gboolean TRUE if str matches the condition in the
476  *		matcher structure
477  */
matcherprop_string_match(MatcherProp * prop,const gchar * str,const gchar * debug_context)478 static gboolean matcherprop_string_match(MatcherProp *prop, const gchar *str,
479 					 const gchar *debug_context)
480 {
481 	gchar *str1;
482 	const gchar *down_expr;
483 	gboolean ret = FALSE;
484 	gboolean should_free = FALSE;
485 	if (str == NULL)
486 		return FALSE;
487 
488 	if (prop->matchtype == MATCHTYPE_REGEXPCASE ||
489 	    prop->matchtype == MATCHTYPE_MATCHCASE) {
490 		str1 = g_utf8_casefold(str, -1);
491 		if (!prop->casefold_expr) {
492 			prop->casefold_expr = g_utf8_casefold(prop->expr, -1);
493 		}
494 		down_expr = prop->casefold_expr;
495 
496 		should_free = TRUE;
497 	} else {
498 		str1 = (gchar *)str;
499 		down_expr = (gchar *)prop->expr;
500 		should_free = FALSE;
501 	}
502 
503 	switch (prop->matchtype) {
504 	case MATCHTYPE_REGEXPCASE:
505 	case MATCHTYPE_REGEXP:
506 		if (!prop->preg && (prop->error == 0)) {
507 			prop->preg = g_new0(regex_t, 1);
508 			/* if regexp then don't use the escaped string */
509 			if (regcomp(prop->preg, down_expr,
510 				    REG_NOSUB | REG_EXTENDED
511 				    | ((prop->matchtype == MATCHTYPE_REGEXPCASE)
512 				    ? REG_ICASE : 0)) != 0) {
513 				prop->error = 1;
514 				g_free(prop->preg);
515 				prop->preg = NULL;
516 			}
517 		}
518 		if (prop->preg == NULL) {
519 			ret = FALSE;
520 			goto free_strs;
521 		}
522 
523 		if (regexec(prop->preg, str1, 0, NULL, 0) == 0)
524 			ret = TRUE;
525 		else
526 			ret = FALSE;
527 
528 		/* debug output */
529 		if (debug_filtering_session
530 				&& prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
531 			gchar *stripped = g_strdup(str);
532 
533 			strretchomp(stripped);
534 			if (ret) {
535 				log_print(LOG_DEBUG_FILTERING,
536 						"%s value [ %s ] matches regular expression [ %s ] (%s)\n",
537 						debug_context, stripped, prop->expr,
538 						prop->matchtype == MATCHTYPE_REGEXP ? _("Case sensitive"):_("Case insensitive"));
539 			} else {
540 				log_print(LOG_DEBUG_FILTERING,
541 						"%s value [ %s ] does NOT match regular expression [ %s ] (%s)\n",
542 						debug_context, stripped, prop->expr,
543 						prop->matchtype == MATCHTYPE_REGEXP ? _("Case sensitive"):_("Case insensitive"));
544 			}
545 			g_free(stripped);
546 		}
547 		break;
548 	case MATCHTYPE_MATCHCASE:
549 	case MATCHTYPE_MATCH:
550 		ret = (strstr(str1, down_expr) != NULL);
551 
552 		/* debug output */
553 		if (debug_filtering_session
554 				&& prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
555 			gchar *stripped = g_strdup(str);
556 
557 			strretchomp(stripped);
558 			if (ret) {
559 				log_print(LOG_DEBUG_FILTERING,
560 						"%s value [ %s ] contains [ %s ] (%s)\n",
561 						debug_context, stripped, prop->expr,
562 						prop->matchtype == MATCHTYPE_MATCH ? _("Case sensitive"):_("Case insensitive"));
563 			} else {
564 				log_print(LOG_DEBUG_FILTERING,
565 						"%s value [ %s ] does NOT contain [ %s ] (%s)\n",
566 						debug_context, stripped, prop->expr,
567 						prop->matchtype == MATCHTYPE_MATCH ? _("Case sensitive"):_("Case insensitive"));
568 			}
569 			g_free(stripped);
570 		}
571 		break;
572 
573 	default:
574 		break;
575 	}
576 
577 free_strs:
578 	if (should_free) {
579 		g_free(str1);
580 	}
581 	return ret;
582 }
583 
584 /*!
585  *\brief	Find out if a tag matches a condition
586  *
587  *\param	prop Matcher structure
588  *\param	msginfo message to check
589  *
590  *\return	gboolean TRUE if msginfo matches the condition in the
591  *		matcher structure
592  */
matcherprop_tag_match(MatcherProp * prop,MsgInfo * msginfo,const gchar * debug_context)593 static gboolean matcherprop_tag_match(MatcherProp *prop, MsgInfo *msginfo,
594 					 const gchar *debug_context)
595 {
596 	gboolean ret = FALSE;
597 	GSList *cur;
598 
599 	if (msginfo == NULL || msginfo->tags == NULL)
600 		return FALSE;
601 
602 	for (cur = msginfo->tags; cur; cur = cur->next) {
603 		const gchar *str = tags_get_tag(GPOINTER_TO_INT(cur->data));
604 		if (!str)
605 			continue;
606 		if (matcherprop_string_match(prop, str, debug_context)) {
607 			ret = TRUE;
608 			break;
609 		}
610 	}
611 	return ret;
612 }
613 
614 /*!
615  *\brief	Find out if the string-ed list matches a condition
616  *
617  *\param	prop Matcher structure
618  *\param	list GSList of strings to check
619  *
620  *\return	gboolean TRUE if str matches the condition in the
621  *		matcher structure
622  */
matcherprop_list_match(MatcherProp * prop,const GSList * list,const gchar * debug_context)623 static gboolean matcherprop_list_match(MatcherProp *prop, const GSList *list,
624 const gchar *debug_context)
625 {
626 	const GSList *cur;
627 
628 	for(cur = list; cur != NULL; cur = cur->next) {
629 		if (matcherprop_string_match(prop, (gchar *)cur->data, debug_context))
630 			return TRUE;
631 	}
632 	return FALSE;
633 }
634 
matcherprop_header_line_match(MatcherProp * prop,const gchar * hdr,const gchar * str,const gboolean both,const gchar * debug_context)635 static gboolean matcherprop_header_line_match(MatcherProp *prop, const gchar *hdr,
636 					 const gchar *str, const gboolean both,
637 					 const gchar *debug_context)
638 {
639 	gboolean res = FALSE;
640 
641 	if (hdr == NULL || str == NULL)
642 		return FALSE;
643 
644 	if (both) {
645 		/* Search in all header names and content.
646 		 */
647 		gchar *line = g_strdup_printf("%s %s", hdr, str);
648 		res = matcherprop_string_match(prop, line, debug_context);
649 		g_free(line);
650 	} else {
651 		/* Search only in content and exclude private headers.
652 		 * E.g.: searching for "H foo" in folder x/foo would return
653 		 * *all* mail as SCF and RMID will match.
654 		 * Searching for "H sent" would return all resent messages
655 		 * as "Resent-From: whatever" will match.
656 		 */
657 		if (procheader_header_is_internal(hdr))
658 			return FALSE;
659 		res = matcherprop_string_match(prop, str, debug_context);
660 	}
661 
662 	return res;
663 }
664 
665 #ifdef USE_PTHREAD
666 typedef struct _thread_data {
667 	const gchar *cmd;
668 	gboolean done;
669 } thread_data;
670 #endif
671 
672 #ifdef USE_PTHREAD
matcher_test_thread(void * data)673 static void *matcher_test_thread(void *data)
674 {
675 	thread_data *td = (thread_data *)data;
676 	int result = -1;
677 
678 	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
679 	pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
680 
681 	result = system(td->cmd);
682 	td->done = TRUE; /* let the caller thread join() */
683 	return GINT_TO_POINTER(result);
684 }
685 #endif
686 
687 /*!
688  *\brief	Execute a command defined in the matcher structure
689  *
690  *\param	prop Pointer to matcher structure
691  *\param	info Pointer to message info structure
692  *
693  *\return	gboolean TRUE if command was executed successfully
694  */
matcherprop_match_test(const MatcherProp * prop,MsgInfo * info)695 static gboolean matcherprop_match_test(const MatcherProp *prop,
696 					  MsgInfo *info)
697 {
698 	gchar *file;
699 	gchar *cmd;
700 	gint retval;
701 #ifdef USE_PTHREAD
702 	pthread_t pt;
703 	thread_data *td = g_new0(thread_data, 1);
704 	void *res = NULL;
705 	time_t start_time = time(NULL);
706 #endif
707 
708 	file = procmsg_get_message_file(info);
709 	if (file == NULL) {
710 #ifdef USE_PTHREAD
711 		g_free(td);
712 #endif
713 		return FALSE;
714 	}
715 	g_free(file);
716 
717 	cmd = matching_build_command(prop->expr, info);
718 	if (cmd == NULL) {
719 #ifdef USE_PTHREAD
720 		g_free(td);
721 #endif
722 		return FALSE;
723 }
724 
725 #ifdef USE_PTHREAD
726 	/* debug output */
727 	if (debug_filtering_session
728 			&& prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
729 		log_print(LOG_DEBUG_FILTERING,
730 				"starting threaded command [ %s ]\n",
731 				cmd);
732 	}
733 
734 	td->cmd = cmd;
735 	td->done = FALSE;
736 	if (pthread_create(&pt, NULL, matcher_test_thread, td) != 0)
737 		retval = system(cmd);
738 	else {
739 		debug_print("waiting for test thread\n");
740 		while(!td->done) {
741 			/* don't let the interface freeze while waiting */
742 			if (time(NULL) - start_time > 0) {
743 				claws_do_idle();
744 			}
745 			if (time(NULL) - start_time > 30) {
746 				pthread_cancel(pt);
747 				td->done = TRUE;
748 				retval = -1;
749 			}
750 		}
751 		pthread_join(pt, &res);
752 		retval = GPOINTER_TO_INT(res);
753 		debug_print(" test thread returned %d\n", retval);
754 	}
755 	g_free(td);
756 #else
757 	/* debug output */
758 	if (debug_filtering_session
759 			&& prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
760 		log_print(LOG_DEBUG_FILTERING,
761 				"starting synchronous command [ %s ]\n",
762 				cmd);
763 	}
764 
765 	retval = system(cmd);
766 #endif
767 	debug_print("Command exit code: %d\n", retval);
768 
769 	/* debug output */
770 	if (debug_filtering_session
771 			&& prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
772 		log_print(LOG_DEBUG_FILTERING,
773 				"command returned [ %d ]\n",
774 				retval);
775 	}
776 
777 	g_free(cmd);
778 	return (retval == 0);
779 }
780 
781 /*!
782  *\brief	Check if a message matches the condition in a matcher
783  *		structure.
784  *
785  *\param	prop Pointer to matcher structure
786  *\param	info Pointer to message info
787  *
788  *\return	gboolean TRUE if a match
789  */
matcherprop_match(MatcherProp * prop,MsgInfo * info)790 static gboolean matcherprop_match(MatcherProp *prop,
791 				  MsgInfo *info)
792 {
793 	time_t t;
794 	gint age_mult_hours = 1;
795 
796 	switch(prop->criteria) {
797 	case MATCHCRITERIA_ALL:
798 		return TRUE;
799 	case MATCHCRITERIA_UNREAD:
800 		return MSG_IS_UNREAD(info->flags);
801 	case MATCHCRITERIA_NOT_UNREAD:
802 		return !MSG_IS_UNREAD(info->flags);
803 	case MATCHCRITERIA_NEW:
804 		return MSG_IS_NEW(info->flags);
805 	case MATCHCRITERIA_NOT_NEW:
806 		return !MSG_IS_NEW(info->flags);
807 	case MATCHCRITERIA_MARKED:
808 		return MSG_IS_MARKED(info->flags);
809 	case MATCHCRITERIA_NOT_MARKED:
810 		return !MSG_IS_MARKED(info->flags);
811 	case MATCHCRITERIA_DELETED:
812 		return MSG_IS_DELETED(info->flags);
813 	case MATCHCRITERIA_NOT_DELETED:
814 		return !MSG_IS_DELETED(info->flags);
815 	case MATCHCRITERIA_REPLIED:
816 		return MSG_IS_REPLIED(info->flags);
817 	case MATCHCRITERIA_NOT_REPLIED:
818 		return !MSG_IS_REPLIED(info->flags);
819 	case MATCHCRITERIA_FORWARDED:
820 		return MSG_IS_FORWARDED(info->flags);
821 	case MATCHCRITERIA_NOT_FORWARDED:
822 		return !MSG_IS_FORWARDED(info->flags);
823 	case MATCHCRITERIA_LOCKED:
824 		return MSG_IS_LOCKED(info->flags);
825 	case MATCHCRITERIA_NOT_LOCKED:
826 		return !MSG_IS_LOCKED(info->flags);
827 	case MATCHCRITERIA_SPAM:
828 		return MSG_IS_SPAM(info->flags);
829 	case MATCHCRITERIA_NOT_SPAM:
830 		return !MSG_IS_SPAM(info->flags);
831 	case MATCHCRITERIA_HAS_ATTACHMENT:
832 		return MSG_IS_WITH_ATTACHMENT(info->flags);
833 	case MATCHCRITERIA_HAS_NO_ATTACHMENT:
834 		return !MSG_IS_WITH_ATTACHMENT(info->flags);
835 	case MATCHCRITERIA_SIGNED:
836 		return MSG_IS_SIGNED(info->flags);
837 	case MATCHCRITERIA_NOT_SIGNED:
838 		return !MSG_IS_SIGNED(info->flags);
839 	case MATCHCRITERIA_COLORLABEL:
840 	{
841 		gint color = MSG_GET_COLORLABEL_VALUE(info->flags);
842 		gboolean ret = (color == prop->value);
843 
844 		/* debug output */
845 		if (debug_filtering_session
846 				&& prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
847 			if (ret) {
848 				log_print(LOG_DEBUG_FILTERING,
849 						"message color value [ %d ] matches color value [ %d ]\n",
850 						color, prop->value);
851 			} else {
852 				log_print(LOG_DEBUG_FILTERING,
853 						"message color value [ %d ] does NOT match color value [ %d ]\n",
854 						color, prop->value);
855 			}
856 		}
857 		return ret;
858 	}
859 	case MATCHCRITERIA_NOT_COLORLABEL:
860 	{
861 		gint color = MSG_GET_COLORLABEL_VALUE(info->flags);
862 		gboolean ret = (color != prop->value);
863 
864 		/* debug output */
865 		if (debug_filtering_session
866 				&& prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
867 			if (ret) {
868 				log_print(LOG_DEBUG_FILTERING,
869 						"message color value [ %d ] matches color value [ %d ]\n",
870 						color, prop->value);
871 			} else {
872 				log_print(LOG_DEBUG_FILTERING,
873 						"message color value [ %d ] does NOT match color value [ %d ]\n",
874 						color, prop->value);
875 			}
876 		}
877 		return ret;
878 	}
879 	case MATCHCRITERIA_IGNORE_THREAD:
880 		return MSG_IS_IGNORE_THREAD(info->flags);
881 	case MATCHCRITERIA_NOT_IGNORE_THREAD:
882 		return !MSG_IS_IGNORE_THREAD(info->flags);
883 	case MATCHCRITERIA_WATCH_THREAD:
884 		return MSG_IS_WATCH_THREAD(info->flags);
885 	case MATCHCRITERIA_NOT_WATCH_THREAD:
886 		return !MSG_IS_WATCH_THREAD(info->flags);
887 	case MATCHCRITERIA_SUBJECT:
888 		return matcherprop_string_match(prop, info->subject, context_str[CONTEXT_SUBJECT]);
889 	case MATCHCRITERIA_NOT_SUBJECT:
890 		return !matcherprop_string_match(prop, info->subject, context_str[CONTEXT_SUBJECT]);
891 	case MATCHCRITERIA_FROM:
892 		return matcherprop_string_match(prop, info->from, context_str[CONTEXT_FROM]);
893 	case MATCHCRITERIA_NOT_FROM:
894 		return !matcherprop_string_match(prop, info->from, context_str[CONTEXT_FROM]);
895 	case MATCHCRITERIA_TO:
896 		return matcherprop_string_match(prop, info->to, context_str[CONTEXT_TO]);
897 	case MATCHCRITERIA_NOT_TO:
898 		return !matcherprop_string_match(prop, info->to, context_str[CONTEXT_TO]);
899 	case MATCHCRITERIA_CC:
900 		return matcherprop_string_match(prop, info->cc, context_str[CONTEXT_CC]);
901 	case MATCHCRITERIA_NOT_CC:
902 		return !matcherprop_string_match(prop, info->cc, context_str[CONTEXT_CC]);
903 	case MATCHCRITERIA_TO_OR_CC:
904 		return matcherprop_string_match(prop, info->to, context_str[CONTEXT_TO])
905 		     || matcherprop_string_match(prop, info->cc, context_str[CONTEXT_CC]);
906 	case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
907 		return !matcherprop_string_match(prop, info->to, context_str[CONTEXT_TO])
908 		     && !matcherprop_string_match(prop, info->cc, context_str[CONTEXT_CC]);
909 	case MATCHCRITERIA_TAG:
910 		return matcherprop_tag_match(prop, info, context_str[CONTEXT_TAG]);
911 	case MATCHCRITERIA_NOT_TAG:
912 		return !matcherprop_tag_match(prop, info, context_str[CONTEXT_TAG]);
913 	case MATCHCRITERIA_TAGGED:
914 		return info->tags != NULL;
915 	case MATCHCRITERIA_NOT_TAGGED:
916 		return info->tags == NULL;
917 	case MATCHCRITERIA_AGE_GREATER:
918 		age_mult_hours = 24;
919 		/* Fallthrough intended */
920 	case MATCHCRITERIA_AGE_GREATER_HOURS:
921 	{
922 		gboolean ret;
923 		gint age;
924 
925 		t = time(NULL);
926 		age = ((t - info->date_t) / (60 * 60 * age_mult_hours));
927 		ret = (age >= prop->value);
928 
929 		/* debug output */
930 		if (debug_filtering_session
931 				&& prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
932 			if (ret) {
933 				log_print(LOG_DEBUG_FILTERING,
934 						"message age [ %d ] is greater than [ %d ]\n",
935 						age, prop->value);
936 			} else {
937 				log_print(LOG_DEBUG_FILTERING,
938 						"message age [ %d ] is not greater than [ %d ]\n",
939 						age, prop->value);
940 			}
941 		}
942 		return ret;
943 	}
944 	case MATCHCRITERIA_DATE_AFTER:
945 	{
946 		gboolean ret;
947 
948 		ret = prop->value < info->date_t;
949 
950 		/* debug output */
951 		if (debug_filtering_session
952 				&& prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
953 			if (ret) {
954 				log_print(LOG_DEBUG_FILTERING,
955 						"message date [ %"G_GSIZE_FORMAT" ] is after [ %d ]\n",
956 						info->date_t, prop->value);
957 			} else {
958 				log_print(LOG_DEBUG_FILTERING,
959 						"message date [ %ld ] is not after [ %d ]\n",
960 						info->date_t, prop->value);
961 			}
962 		}
963 		return ret;
964 	}
965 	case MATCHCRITERIA_AGE_LOWER:
966 		age_mult_hours = 24;
967 		/* Fallthrough intended */
968 	case MATCHCRITERIA_AGE_LOWER_HOURS:
969 	{
970 		gboolean ret;
971 		gint age;
972 
973 		t = time(NULL);
974 		age = ((t - info->date_t) / (60 * 60 * age_mult_hours));
975 		ret = (age < prop->value);
976 
977 		/* debug output */
978 		if (debug_filtering_session
979 				&& prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
980 			if (ret) {
981 				log_print(LOG_DEBUG_FILTERING,
982 						"message age [ %d ] is lower than [ %d ]\n",
983 						age, prop->value);
984 			} else {
985 				log_print(LOG_DEBUG_FILTERING,
986 						"message age [ %d ] is not lower than [ %d ]\n",
987 						age, prop->value);
988 			}
989 		}
990 		return ret;
991 	}
992 	case MATCHCRITERIA_DATE_BEFORE:
993 	{
994 		gboolean ret;
995 
996 		ret = prop->value > info->date_t;
997 
998 		/* debug output */
999 		if (debug_filtering_session
1000 				&& prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1001 			if (ret) {
1002 				log_print(LOG_DEBUG_FILTERING,
1003 						"message date [ %ld ] is before [ %d ]\n",
1004 						info->date_t, prop->value);
1005 			} else {
1006 				log_print(LOG_DEBUG_FILTERING,
1007 						"message date [ %ld ] is not before [ %d ]\n",
1008 						info->date_t, prop->value);
1009 			}
1010 		}
1011 		return ret;
1012 	}
1013 	case MATCHCRITERIA_SCORE_GREATER:
1014 	{
1015 		gboolean ret = (info->score > prop->value);
1016 
1017 		/* debug output */
1018 		if (debug_filtering_session
1019 				&& prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1020 			if (ret) {
1021 				log_print(LOG_DEBUG_FILTERING,
1022 						"message score [ %d ] is greater than [ %d ]\n",
1023 						info->score, prop->value);
1024 			} else {
1025 				log_print(LOG_DEBUG_FILTERING,
1026 						"message score [ %d ] is not greater than [ %d ]\n",
1027 						info->score, prop->value);
1028 			}
1029 		}
1030 		return ret;
1031 	}
1032 	case MATCHCRITERIA_SCORE_LOWER:
1033 	{
1034 		gboolean ret = (info->score < prop->value);
1035 
1036 		/* debug output */
1037 		if (debug_filtering_session
1038 				&& prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1039 			if (ret) {
1040 				log_print(LOG_DEBUG_FILTERING,
1041 						"message score [ %d ] is lower than [ %d ]\n",
1042 						info->score, prop->value);
1043 			} else {
1044 				log_print(LOG_DEBUG_FILTERING,
1045 						"message score [ %d ] is not lower than [ %d ]\n",
1046 						info->score, prop->value);
1047 			}
1048 		}
1049 		return ret;
1050 	}
1051 	case MATCHCRITERIA_SCORE_EQUAL:
1052 	{
1053 		gboolean ret = (info->score == prop->value);
1054 
1055 		/* debug output */
1056 		if (debug_filtering_session
1057 				&& prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1058 			if (ret) {
1059 				log_print(LOG_DEBUG_FILTERING,
1060 						"message score [ %d ] is equal to [ %d ]\n",
1061 						info->score, prop->value);
1062 			} else {
1063 				log_print(LOG_DEBUG_FILTERING,
1064 						"message score [ %d ] is not equal to [ %d ]\n",
1065 						info->score, prop->value);
1066 			}
1067 		}
1068 		return ret;
1069 	}
1070 	case MATCHCRITERIA_SIZE_GREATER:
1071 	{
1072 		/* FIXME: info->size is a goffset */
1073 		gboolean ret = (info->size > (goffset) prop->value);
1074 
1075 		/* debug output */
1076 		if (debug_filtering_session
1077 				&& prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1078 			if (ret) {
1079 				log_print(LOG_DEBUG_FILTERING,
1080 						"message size is greater than [ %d ]\n",
1081 						prop->value);
1082 			} else {
1083 				log_print(LOG_DEBUG_FILTERING,
1084 						"message size is not greater than [ %d ]\n",
1085 						prop->value);
1086 			}
1087 		}
1088 		return ret;
1089 	}
1090 	case MATCHCRITERIA_SIZE_SMALLER:
1091 	{
1092 		/* FIXME: info->size is a goffset */
1093 		gboolean ret = (info->size < (goffset) prop->value);
1094 
1095 		/* debug output */
1096 		if (debug_filtering_session
1097 				&& prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1098 			if (ret) {
1099 				log_print(LOG_DEBUG_FILTERING,
1100 						"message size is smaller than [ %d ]\n",
1101 						prop->value);
1102 			} else {
1103 				log_print(LOG_DEBUG_FILTERING,
1104 						"message size is not smaller than [ %d ]\n",
1105 						prop->value);
1106 			}
1107 		}
1108 		return ret;
1109 	}
1110 	case MATCHCRITERIA_SIZE_EQUAL:
1111 	{
1112 		/* FIXME: info->size is a goffset */
1113 		gboolean ret = (info->size == (goffset) prop->value);
1114 
1115 		/* debug output */
1116 		if (debug_filtering_session
1117 				&& prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1118 			if (ret) {
1119 				log_print(LOG_DEBUG_FILTERING,
1120 						"message size is equal to [ %d ]\n",
1121 						prop->value);
1122 			} else {
1123 				log_print(LOG_DEBUG_FILTERING,
1124 						"message size is not equal to [ %d ]\n",
1125 						prop->value);
1126 			}
1127 		}
1128 		return ret;
1129 	}
1130 	case MATCHCRITERIA_PARTIAL:
1131 	{
1132 		/* FIXME: info->size is a goffset */
1133 		gboolean ret = (info->total_size != 0 && info->size != (goffset)info->total_size);
1134 
1135 		/* debug output */
1136 		if (debug_filtering_session
1137 				&& prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1138 			if (ret) {
1139 				log_print(LOG_DEBUG_FILTERING,
1140 						"message is partially downloaded, size is less than total size [ %d ])\n",
1141 						info->total_size);
1142 			} else {
1143 				log_print(LOG_DEBUG_FILTERING,
1144 						"message is not partially downloaded\n");
1145 			}
1146 		}
1147 		return ret;
1148 	}
1149 	case MATCHCRITERIA_NOT_PARTIAL:
1150 	{
1151 		/* FIXME: info->size is a goffset */
1152 		gboolean ret = (info->total_size == 0 || info->size == (goffset)info->total_size);
1153 
1154 		/* debug output */
1155 		if (debug_filtering_session
1156 				&& prefs_common.filtering_debug_level >= FILTERING_DEBUG_LEVEL_HIGH) {
1157 			if (ret) {
1158 				log_print(LOG_DEBUG_FILTERING,
1159 						"message is not partially downloaded\n");
1160 			} else {
1161 				log_print(LOG_DEBUG_FILTERING,
1162 						"message is partially downloaded, size is less than total size [ %d ])\n",
1163 						info->total_size);
1164 			}
1165 		}
1166 		return ret;
1167 	}
1168 	case MATCHCRITERIA_NEWSGROUPS:
1169 		return matcherprop_string_match(prop, info->newsgroups, context_str[CONTEXT_NEWSGROUPS]);
1170 	case MATCHCRITERIA_NOT_NEWSGROUPS:
1171 		return !matcherprop_string_match(prop, info->newsgroups, context_str[CONTEXT_NEWSGROUPS]);
1172 	case MATCHCRITERIA_MESSAGEID:
1173 		return matcherprop_string_match(prop, info->msgid, context_str[CONTEXT_MESSAGEID]);
1174 	case MATCHCRITERIA_NOT_MESSAGEID:
1175 		return !matcherprop_string_match(prop, info->msgid, context_str[CONTEXT_MESSAGEID]);
1176 	case MATCHCRITERIA_INREPLYTO:
1177 		return matcherprop_string_match(prop, info->inreplyto, context_str[CONTEXT_IN_REPLY_TO]);
1178 	case MATCHCRITERIA_NOT_INREPLYTO:
1179 		return !matcherprop_string_match(prop, info->inreplyto, context_str[CONTEXT_IN_REPLY_TO]);
1180 	case MATCHCRITERIA_REFERENCES:
1181 		return matcherprop_list_match(prop, info->references, context_str[CONTEXT_REFERENCES]);
1182 	case MATCHCRITERIA_NOT_REFERENCES:
1183 		return !matcherprop_list_match(prop, info->references, context_str[CONTEXT_REFERENCES]);
1184 	case MATCHCRITERIA_TEST:
1185 		return matcherprop_match_test(prop, info);
1186 	case MATCHCRITERIA_NOT_TEST:
1187 		return !matcherprop_match_test(prop, info);
1188 	default:
1189 		return FALSE;
1190 	}
1191 }
1192 
1193 /* ********************* MatcherList *************************** */
1194 
1195 /*!
1196  *\brief	Create a new list of matchers
1197  *
1198  *\param	matchers List of matcher structures
1199  *\param	bool_and Operator
1200  *
1201  *\return	MatcherList * New list
1202  */
matcherlist_new(GSList * matchers,gboolean bool_and)1203 MatcherList *matcherlist_new(GSList *matchers, gboolean bool_and)
1204 {
1205 	MatcherList *cond;
1206 
1207 	cond = g_new0(MatcherList, 1);
1208 
1209 	cond->matchers = matchers;
1210 	cond->bool_and = bool_and;
1211 
1212 	return cond;
1213 }
1214 
1215 #ifdef G_OS_UNIX
1216 /*!
1217  *\brief	Builds a single regular expresion from an array of srings.
1218  *
1219  *\param	strings The lines containing the different sub-regexp.
1220  *
1221  *\return	The newly allocated regexp string.
1222  */
build_complete_regexp(gchar ** strings)1223 static gchar *build_complete_regexp(gchar **strings)
1224 {
1225 	int i = 0;
1226 	gchar *expr = NULL;
1227 	while (strings && strings[i] && *strings[i]) {
1228 		int old_len = expr ? strlen(expr):0;
1229 		int new_len = 0;
1230 		gchar *tmpstr = NULL;
1231 
1232 		if (g_utf8_validate(strings[i], -1, NULL))
1233 			tmpstr = g_strdup(strings[i]);
1234 		else
1235 			tmpstr = conv_codeset_strdup(strings[i],
1236 					conv_get_locale_charset_str_no_utf8(),
1237 				 	CS_INTERNAL);
1238 
1239 		if (strstr(tmpstr, "\n"))
1240 			*(strstr(tmpstr, "\n")) = '\0';
1241 
1242 		new_len = strlen(tmpstr);
1243 
1244 		expr = g_realloc(expr,
1245 			expr ? (old_len + strlen("|()") + new_len + 1)
1246 			     : (strlen("()") + new_len + 1));
1247 
1248 		if (old_len) {
1249 			strcpy(expr + old_len, "|(");
1250 			strcpy(expr + old_len + 2, tmpstr);
1251 			strcpy(expr + old_len + 2 + new_len, ")");
1252 		} else {
1253 			strcpy(expr+old_len, "(");
1254 			strcpy(expr+old_len + 1, tmpstr);
1255 			strcpy(expr+old_len + 1 + new_len, ")");
1256 		}
1257 		g_free(tmpstr);
1258 		i++;
1259 	}
1260 	return expr;
1261 }
1262 #endif
1263 
1264 /*!
1265  *\brief	Create a new list of matchers from a multi-line string
1266  *
1267  *\param	lines String with "\n"-separated expressions
1268  *\param	bool_and Operator
1269  *\param	case_sensitive If the matching is case sensitive or not
1270  *
1271  *\return	MatcherList * New matcher list
1272  */
matcherlist_new_from_lines(gchar * lines,gboolean bool_and,gboolean case_sensitive)1273 MatcherList *matcherlist_new_from_lines(gchar *lines, gboolean bool_and,
1274 					gboolean case_sensitive)
1275 {
1276 	MatcherProp *m = NULL;
1277 	GSList *matchers = NULL;
1278 	gchar **strings = g_strsplit(lines, "\n", -1);
1279 
1280 #ifdef G_OS_UNIX
1281 	gchar *expr = NULL;
1282 	expr = build_complete_regexp(strings);
1283 	debug_print("building matcherprop for expr '%s'\n", expr?expr:"NULL");
1284 
1285 	m = matcherprop_new(MATCHCRITERIA_SUBJECT, NULL,
1286 			case_sensitive? MATCHTYPE_REGEXP: MATCHTYPE_REGEXPCASE,
1287 			expr, 0);
1288 	if (m == NULL) {
1289 		/* print error message */
1290 		debug_print("failed to allocate memory for matcherprop\n");
1291 	} else {
1292 		matchers = g_slist_append(matchers, m);
1293 	}
1294 
1295 	g_free(expr);
1296 #else
1297 	int i = 0;
1298 	while (strings && strings[i] && *strings[i]) {
1299 		m = matcherprop_new(MATCHCRITERIA_SUBJECT, NULL,
1300 			case_sensitive? MATCHTYPE_MATCH: MATCHTYPE_MATCHCASE,
1301 			strings[i], 0);
1302 		if (m == NULL) {
1303 			/* print error message */
1304 			debug_print("failed to allocate memory for matcherprop\n");
1305 		} else {
1306 			matchers = g_slist_append(matchers, m);
1307 		}
1308 		i++;
1309 	}
1310 #endif
1311 	g_strfreev(strings);
1312 
1313 	return matcherlist_new(matchers, bool_and);
1314 }
1315 
1316 /*!
1317  *\brief	Frees a list of matchers
1318  *
1319  *\param	cond List of matchers
1320  */
matcherlist_free(MatcherList * cond)1321 void matcherlist_free(MatcherList *cond)
1322 {
1323 	GSList *l;
1324 
1325 	cm_return_if_fail(cond);
1326 	for (l = cond->matchers ; l != NULL ; l = g_slist_next(l)) {
1327 		matcherprop_free((MatcherProp *) l->data);
1328 	}
1329 	g_slist_free(cond->matchers);
1330 	g_free(cond);
1331 }
1332 
1333 /*!
1334  *\brief	Check if a header matches a matcher condition
1335  *
1336  *\param	matcher Matcher structure to check header for
1337  *\param	buf Header name
1338  *
1339  *\return	boolean TRUE if matching header
1340  */
matcherprop_match_one_header(MatcherProp * matcher,gchar * buf)1341 static gboolean matcherprop_match_one_header(MatcherProp *matcher,
1342 					     gchar *buf)
1343 {
1344 	gboolean result = FALSE;
1345 	Header *header = NULL;
1346 
1347 	switch (matcher->criteria) {
1348 	case MATCHCRITERIA_HEADER:
1349 	case MATCHCRITERIA_NOT_HEADER:
1350 		header = procheader_parse_header(buf);
1351 		if (!header)
1352 			return FALSE;
1353 		if (procheader_headername_equal(header->name,
1354 						matcher->header)) {
1355 			if (matcher->criteria == MATCHCRITERIA_HEADER)
1356 				result = matcherprop_string_match(matcher, header->body, context_str[CONTEXT_HEADER]);
1357 			else
1358 				result = !matcherprop_string_match(matcher, header->body, context_str[CONTEXT_HEADER]);
1359 			procheader_header_free(header);
1360 			return result;
1361 		}
1362 		else {
1363 			procheader_header_free(header);
1364 		}
1365 		break;
1366 	case MATCHCRITERIA_HEADERS_PART:
1367 	case MATCHCRITERIA_HEADERS_CONT:
1368 	case MATCHCRITERIA_MESSAGE:
1369 		header = procheader_parse_header(buf);
1370 		if (!header)
1371 			return FALSE;
1372 		result = matcherprop_header_line_match(matcher,
1373 			       header->name, header->body,
1374 			       (matcher->criteria == MATCHCRITERIA_HEADERS_PART),
1375 			       context_str[CONTEXT_HEADER_LINE]);
1376 		procheader_header_free(header);
1377 		return result;
1378 	case MATCHCRITERIA_NOT_HEADERS_CONT:
1379 	case MATCHCRITERIA_NOT_HEADERS_PART:
1380 	case MATCHCRITERIA_NOT_MESSAGE:
1381 		header = procheader_parse_header(buf);
1382 		if (!header)
1383 			return FALSE;
1384 		result = !matcherprop_header_line_match(matcher,
1385 			       header->name, header->body,
1386 			       (matcher->criteria == MATCHCRITERIA_NOT_HEADERS_PART),
1387 			       context_str[CONTEXT_HEADER_LINE]);
1388 		procheader_header_free(header);
1389 		return result;
1390 	case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK:
1391 	case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK:
1392 		{
1393 			GSList *address_list = NULL;
1394 			gint match = MATCH_ONE;
1395 			gboolean found = FALSE;
1396 
1397 			/* how many address headers are we trying to match? */
1398 			if (strcasecmp(matcher->header, "Any") == 0)
1399 				match = MATCH_ANY;
1400 			else if (strcasecmp(matcher->header, "All") == 0)
1401 					match = MATCH_ALL;
1402 
1403 			if (match == MATCH_ONE) {
1404 				/* matching one address header exactly, is that the right one? */
1405 				header = procheader_parse_header(buf);
1406 				if (!header ||
1407 						!procheader_headername_equal(header->name, matcher->header)) {
1408 					procheader_header_free(header);
1409 					return FALSE;
1410 				}
1411 				address_list = address_list_append(address_list, header->body);
1412 				if (address_list == NULL) {
1413 					procheader_header_free(header);
1414 					return FALSE;
1415 				}
1416 				procheader_header_free(header);
1417 
1418 			} else {
1419 				header = procheader_parse_header(buf);
1420 				if (!header)
1421 					return FALSE;
1422 				/* address header is one of the headers we have to match when checking
1423 				   for any address header or all address headers? */
1424 				if (procheader_headername_equal(header->name, "From") ||
1425 					 procheader_headername_equal(header->name, "To") ||
1426 					 procheader_headername_equal(header->name, "Cc") ||
1427 					 procheader_headername_equal(header->name, "Reply-To") ||
1428 					 procheader_headername_equal(header->name, "Sender") ||
1429 					 procheader_headername_equal(header->name, "Resent-From") ||
1430 					 procheader_headername_equal(header->name, "Resent-To"))
1431 					address_list = address_list_append(address_list, header->body);
1432 				procheader_header_free(header);
1433 				if (address_list == NULL)
1434 					return FALSE;
1435 			}
1436 
1437 			found = match_with_addresses_in_addressbook
1438 							(matcher, address_list, matcher->criteria,
1439 							 matcher->expr, match);
1440 			g_slist_free(address_list);
1441 
1442 			if (matcher->criteria == MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK)
1443 				return !found;
1444 			else
1445 				return found;
1446 	}
1447 	}
1448 
1449 	return FALSE;
1450 }
1451 
1452 /*!
1453  *\brief	Check if the matcher structure wants headers to
1454  *		be matched
1455  *
1456  *\param	matcher Matcher structure
1457  *
1458  *\return	gboolean TRUE if the matcher structure describes
1459  *		a header match condition
1460  */
matcherprop_criteria_headers(const MatcherProp * matcher)1461 static gboolean matcherprop_criteria_headers(const MatcherProp *matcher)
1462 {
1463 	switch (matcher->criteria) {
1464 	case MATCHCRITERIA_HEADER:
1465 	case MATCHCRITERIA_NOT_HEADER:
1466 	case MATCHCRITERIA_HEADERS_PART:
1467 	case MATCHCRITERIA_HEADERS_CONT:
1468 	case MATCHCRITERIA_NOT_HEADERS_PART:
1469 	case MATCHCRITERIA_NOT_HEADERS_CONT:
1470 	case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK:
1471 	case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK:
1472 		return TRUE;
1473 	default:
1474 		return FALSE;
1475 	}
1476 }
1477 
1478 /*!
1479  *\brief	Check if the matcher structure wants the message
1480  *		to be matched (just perform an action on any
1481  *		message)
1482  *
1483  *\param	matcher Matcher structure
1484  *
1485  *\return	gboolean TRUE if matcher condition should match
1486  *		a message
1487  */
matcherprop_criteria_message(MatcherProp * matcher)1488 static gboolean matcherprop_criteria_message(MatcherProp *matcher)
1489 {
1490 	switch (matcher->criteria) {
1491 	case MATCHCRITERIA_MESSAGE:
1492 	case MATCHCRITERIA_NOT_MESSAGE:
1493 		return TRUE;
1494 	default:
1495 		return FALSE;
1496 	}
1497 }
1498 
1499 /*!
1500  *\brief	Check if a list of conditions matches one header in
1501  *		a message file.
1502  *
1503  *\param	matchers List of conditions
1504  *\param	fp Message file
1505  *
1506  *\return	gboolean TRUE if one of the headers is matched by
1507  *		the list of conditions.
1508  */
matcherlist_match_headers(MatcherList * matchers,FILE * fp)1509 static gboolean matcherlist_match_headers(MatcherList *matchers, FILE *fp)
1510 {
1511 	GSList *l;
1512 	gchar *buf = NULL;
1513 	gint ret;
1514 
1515 	while ((ret = procheader_get_one_field(&buf, fp, NULL)) != -1) {
1516 		for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
1517 			MatcherProp *matcher = (MatcherProp *) l->data;
1518 			gint match = MATCH_ANY;
1519 
1520 			if (matcher->done)
1521 				continue;
1522 
1523 			/* determine the match range (all, any are our concern here) */
1524 			if (matcher->criteria == MATCHCRITERIA_NOT_HEADERS_PART ||
1525 			    matcher->criteria == MATCHCRITERIA_NOT_HEADERS_CONT ||
1526 			    matcher->criteria == MATCHCRITERIA_NOT_MESSAGE) {
1527 				match = MATCH_ALL;
1528 
1529 			} else if (matcher->criteria == MATCHCRITERIA_FOUND_IN_ADDRESSBOOK ||
1530 			 		   matcher->criteria == MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK) {
1531 				Header *header = NULL;
1532 
1533 				/* address header is one of the headers we have to match when checking
1534 				   for any address header or all address headers? */
1535 				header = procheader_parse_header(buf);
1536 				if (header &&
1537 					(procheader_headername_equal(header->name, "From") ||
1538 					 procheader_headername_equal(header->name, "To") ||
1539 					 procheader_headername_equal(header->name, "Cc") ||
1540 					 procheader_headername_equal(header->name, "Reply-To") ||
1541 					 procheader_headername_equal(header->name, "Sender") ||
1542 					 procheader_headername_equal(header->name, "Resent-From") ||
1543 					 procheader_headername_equal(header->name, "Resent-To"))) {
1544 
1545 					if (strcasecmp(matcher->header, "Any") == 0)
1546 						match = MATCH_ANY;
1547 					else if (strcasecmp(matcher->header, "All") == 0)
1548 						match = MATCH_ALL;
1549 					else
1550 						match = MATCH_ONE;
1551 				} else {
1552 					/* further call to matcherprop_match_one_header() can't match
1553 					   and it irrelevant, so: don't alter the match result */
1554 					procheader_header_free(header);
1555 					continue;
1556 				}
1557 				procheader_header_free(header);
1558 			}
1559 
1560 			/* ZERO line must NOT match for the rule to match.
1561 			 */
1562 			if (match == MATCH_ALL) {
1563 				if (matcherprop_match_one_header(matcher, buf)) {
1564 					matcher->result = TRUE;
1565 				} else {
1566 					matcher->result = FALSE;
1567 					matcher->done = TRUE;
1568 				}
1569 			/* else, just one line matching is enough for the rule to match
1570 			 */
1571 			} else if (matcherprop_criteria_headers(matcher) ||
1572 			           matcherprop_criteria_message(matcher)) {
1573 				if (matcherprop_match_one_header(matcher, buf)) {
1574 					matcher->result = TRUE;
1575 					matcher->done = TRUE;
1576 				}
1577 			}
1578 
1579 			/* if the rule matched and the matchers are OR, no need to
1580 			 * check the others */
1581 			if (matcher->result && matcher->done) {
1582 				if (!matchers->bool_and) {
1583 					g_free(buf);
1584 					return TRUE;
1585 				}
1586 			}
1587 		}
1588 		g_free(buf);
1589 		buf = NULL;
1590 	}
1591 
1592 	return FALSE;
1593 }
1594 
1595 /*!
1596  *\brief	Check if a matcher wants to check the message body
1597  *
1598  *\param	matcher Matcher structure
1599  *
1600  *\return	gboolean TRUE if body must be matched.
1601  */
matcherprop_criteria_body(const MatcherProp * matcher)1602 static gboolean matcherprop_criteria_body(const MatcherProp *matcher)
1603 {
1604 	switch (matcher->criteria) {
1605 	case MATCHCRITERIA_BODY_PART:
1606 	case MATCHCRITERIA_NOT_BODY_PART:
1607 		return TRUE;
1608 	default:
1609 		return FALSE;
1610 	}
1611 }
1612 
matcherlist_match_binary_content(MatcherList * matchers,MimeInfo * partinfo)1613 static gboolean matcherlist_match_binary_content(MatcherList *matchers, MimeInfo *partinfo)
1614 {
1615 	FILE *outfp;
1616 	gchar buf[BUFFSIZE];
1617 	GSList *l;
1618 
1619 	if (!partinfo || partinfo->type == MIMETYPE_TEXT)
1620 		return FALSE;
1621 	else
1622 		outfp = procmime_get_binary_content(partinfo);
1623 
1624 	if (!outfp)
1625 		return FALSE;
1626 
1627 	while (claws_fgets(buf, sizeof(buf), outfp) != NULL) {
1628 		strretchomp(buf);
1629 
1630 		for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
1631 			MatcherProp *matcher = (MatcherProp *) l->data;
1632 
1633 			if (matcher->done)
1634 				continue;
1635 
1636 			/* Don't scan non-text parts when looking in body, only
1637 			 * when looking in whole message
1638 			 */
1639 			if (matcher->criteria == MATCHCRITERIA_NOT_BODY_PART ||
1640 			    matcher->criteria == MATCHCRITERIA_BODY_PART)
1641 				continue;
1642 
1643 			/* if the criteria is ~body_part or ~message, ZERO lines
1644 			 * must match for the rule to match.
1645 			 */
1646 			if (matcher->criteria == MATCHCRITERIA_NOT_BODY_PART ||
1647 			    matcher->criteria == MATCHCRITERIA_NOT_MESSAGE) {
1648 				if (matcherprop_string_match(matcher, buf,
1649 							context_str[CONTEXT_BODY_LINE])) {
1650 					matcher->result = FALSE;
1651 					matcher->done = TRUE;
1652 				} else
1653 					matcher->result = TRUE;
1654 			/* else, just one line has to match */
1655 			} else if (matcherprop_criteria_body(matcher) ||
1656 				   matcherprop_criteria_message(matcher)) {
1657 				if (matcherprop_string_match(matcher, buf,
1658 							context_str[CONTEXT_BODY_LINE])) {
1659 					matcher->result = TRUE;
1660 					matcher->done = TRUE;
1661 				}
1662 			}
1663 
1664 			/* if the matchers are OR'ed and the rule matched,
1665 			 * no need to check the others. */
1666 			if (matcher->result && matcher->done) {
1667 				if (!matchers->bool_and) {
1668 					claws_fclose(outfp);
1669 					return TRUE;
1670 				}
1671 			}
1672 		}
1673 	}
1674 
1675 	claws_fclose(outfp);
1676 	return FALSE;
1677 }
1678 
match_content_cb(const gchar * buf,gpointer data)1679 static gboolean match_content_cb(const gchar *buf, gpointer data)
1680 {
1681 	MatcherList *matchers = (MatcherList *)data;
1682 	gboolean all_done = TRUE;
1683 	GSList *l;
1684 
1685 	for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
1686 		MatcherProp *matcher = (MatcherProp *) l->data;
1687 
1688 		if (matcher->done)
1689 			continue;
1690 
1691 		/* if the criteria is ~body_part or ~message, ZERO lines
1692 		 * must match for the rule to match.
1693 		 */
1694 		if (matcher->criteria == MATCHCRITERIA_NOT_BODY_PART ||
1695 		    matcher->criteria == MATCHCRITERIA_NOT_MESSAGE) {
1696 			if (matcherprop_string_match(matcher, buf,
1697 						context_str[CONTEXT_BODY_LINE])) {
1698 				matcher->result = FALSE;
1699 				matcher->done = TRUE;
1700 			} else
1701 				matcher->result = TRUE;
1702 		/* else, just one line has to match */
1703 		} else if (matcherprop_criteria_body(matcher) ||
1704 			   matcherprop_criteria_message(matcher)) {
1705 			if (matcherprop_string_match(matcher, buf,
1706 						context_str[CONTEXT_BODY_LINE])) {
1707 				matcher->result = TRUE;
1708 				matcher->done = TRUE;
1709 			}
1710 		}
1711 
1712 		/* if the matchers are OR'ed and the rule matched,
1713 		 * no need to check the others. */
1714 		if (matcher->result && matcher->done) {
1715 			if (!matchers->bool_and) {
1716 				return TRUE;
1717 			}
1718 		}
1719 
1720 		if (!matcher->done)
1721 			all_done = FALSE;
1722 	}
1723 	return all_done;
1724 }
1725 
matcherlist_match_text_content(MatcherList * matchers,MimeInfo * partinfo)1726 static gboolean matcherlist_match_text_content(MatcherList *matchers, MimeInfo *partinfo)
1727 {
1728 	if (partinfo->type != MIMETYPE_TEXT)
1729 		return FALSE;
1730 
1731 	return procmime_scan_text_content(partinfo, match_content_cb, matchers);
1732 }
1733 
1734 /*!
1735  *\brief	Check if a line in a message file's body matches
1736  *		the criteria
1737  *
1738  *\param	matchers List of conditions
1739  *\param	fp Message file
1740  *
1741  *\return	gboolean TRUE if successful match
1742  */
matcherlist_match_body(MatcherList * matchers,gboolean body_only,MsgInfo * info)1743 static gboolean matcherlist_match_body(MatcherList *matchers, gboolean body_only, MsgInfo *info)
1744 {
1745 	MimeInfo *mimeinfo = NULL;
1746 	MimeInfo *partinfo = NULL;
1747 	gboolean first_text_found = FALSE;
1748 
1749 	cm_return_val_if_fail(info != NULL, FALSE);
1750 
1751 	mimeinfo = procmime_scan_message(info);
1752 
1753 	/* Skip headers */
1754 	partinfo = procmime_mimeinfo_next(mimeinfo);
1755 
1756 	for (; partinfo != NULL; partinfo = procmime_mimeinfo_next(partinfo)) {
1757 
1758 		if (partinfo->type != MIMETYPE_TEXT && body_only)
1759 			continue;
1760 
1761 		if (partinfo->type == MIMETYPE_TEXT) {
1762 			first_text_found = TRUE;
1763 			if (matcherlist_match_text_content(matchers, partinfo)) {
1764 				procmime_mimeinfo_free_all(&mimeinfo);
1765 				return TRUE;
1766 			}
1767 		} else if (matcherlist_match_binary_content(matchers, partinfo)) {
1768 			procmime_mimeinfo_free_all(&mimeinfo);
1769 			return TRUE;
1770 		}
1771 
1772 		if (body_only && first_text_found)
1773 			break;
1774 	}
1775 	procmime_mimeinfo_free_all(&mimeinfo);
1776 
1777 	return FALSE;
1778 }
1779 
1780 /*!
1781  *\brief	Check if a message file matches criteria
1782  *
1783  *\param	matchers Criteria
1784  *\param	info Message info
1785  *\param	result Default result
1786  *
1787  *\return	gboolean TRUE if matched
1788  */
matcherlist_match_file(MatcherList * matchers,MsgInfo * info,gboolean result)1789 static gboolean matcherlist_match_file(MatcherList *matchers, MsgInfo *info,
1790 				gboolean result)
1791 {
1792 	gboolean read_headers;
1793 	gboolean read_body;
1794 	gboolean body_only;
1795 	GSList *l;
1796 	FILE *fp;
1797 	gchar *file;
1798 
1799 	/* file need to be read ? */
1800 
1801 	read_headers = FALSE;
1802 	read_body = FALSE;
1803 	body_only = TRUE;
1804 	for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
1805 		MatcherProp *matcher = (MatcherProp *) l->data;
1806 
1807 		if (matcherprop_criteria_headers(matcher))
1808 			read_headers = TRUE;
1809 		if (matcherprop_criteria_body(matcher))
1810 			read_body = TRUE;
1811 		if (matcherprop_criteria_message(matcher)) {
1812 			read_headers = TRUE;
1813 			read_body = TRUE;
1814 			body_only = FALSE;
1815 		}
1816 		matcher->result = FALSE;
1817 		matcher->done = FALSE;
1818 	}
1819 
1820 	if (!read_headers && !read_body)
1821 		return result;
1822 
1823 	file = procmsg_get_message_file_full(info, read_headers, read_body);
1824 	if (file == NULL)
1825 		return FALSE;
1826 
1827 	if ((fp = claws_fopen(file, "rb")) == NULL) {
1828 		FILE_OP_ERROR(file, "claws_fopen");
1829 		g_free(file);
1830 		return result;
1831 	}
1832 
1833 	/* read the headers */
1834 
1835 	if (read_headers) {
1836 		if (matcherlist_match_headers(matchers, fp))
1837 			read_body = FALSE;
1838 	} else {
1839 		procheader_skip_headers(fp);
1840 	}
1841 
1842 	/* read the body */
1843 	if (read_body) {
1844 		matcherlist_match_body(matchers, body_only, info);
1845 	}
1846 
1847 	for (l = matchers->matchers; l != NULL; l = g_slist_next(l)) {
1848 		MatcherProp *matcher = (MatcherProp *) l->data;
1849 
1850 		if (matcherprop_criteria_headers(matcher) ||
1851 		    matcherprop_criteria_body(matcher)	  ||
1852 		    matcherprop_criteria_message(matcher)) {
1853 			if (matcher->result) {
1854 				if (!matchers->bool_and) {
1855 					result = TRUE;
1856 					break;
1857 				}
1858 			}
1859 			else {
1860 				if (matchers->bool_and) {
1861 					result = FALSE;
1862 					break;
1863 				}
1864 			}
1865 		}
1866 	}
1867 
1868 	g_free(file);
1869 
1870 	claws_fclose(fp);
1871 
1872 	return result;
1873 }
1874 
1875 /*!
1876  *\brief	Test list of conditions on a message.
1877  *
1878  *\param	matchers List of conditions
1879  *\param	info Message info
1880  *
1881  *\return	gboolean TRUE if matched
1882  */
matcherlist_match(MatcherList * matchers,MsgInfo * info)1883 gboolean matcherlist_match(MatcherList *matchers, MsgInfo *info)
1884 {
1885 	GSList *l;
1886 	gboolean result;
1887 
1888 	if (!matchers)
1889 		return FALSE;
1890 
1891 	if (matchers->bool_and)
1892 		result = TRUE;
1893 	else
1894 		result = FALSE;
1895 
1896 	/* test the cached elements */
1897 
1898 	for (l = matchers->matchers; l != NULL ;l = g_slist_next(l)) {
1899 		MatcherProp *matcher = (MatcherProp *) l->data;
1900 
1901 		if (debug_filtering_session) {
1902 			gchar *buf = matcherprop_to_string(matcher);
1903 			log_print(LOG_DEBUG_FILTERING, _("checking if message matches [ %s ]\n"), buf);
1904 			g_free(buf);
1905 		}
1906 
1907 		switch(matcher->criteria) {
1908 		case MATCHCRITERIA_ALL:
1909 		case MATCHCRITERIA_UNREAD:
1910 		case MATCHCRITERIA_NOT_UNREAD:
1911 		case MATCHCRITERIA_NEW:
1912 		case MATCHCRITERIA_NOT_NEW:
1913 		case MATCHCRITERIA_MARKED:
1914 		case MATCHCRITERIA_NOT_MARKED:
1915 		case MATCHCRITERIA_DELETED:
1916 		case MATCHCRITERIA_NOT_DELETED:
1917 		case MATCHCRITERIA_REPLIED:
1918 		case MATCHCRITERIA_NOT_REPLIED:
1919 		case MATCHCRITERIA_FORWARDED:
1920 		case MATCHCRITERIA_NOT_FORWARDED:
1921 		case MATCHCRITERIA_LOCKED:
1922 		case MATCHCRITERIA_NOT_LOCKED:
1923 		case MATCHCRITERIA_SPAM:
1924 		case MATCHCRITERIA_NOT_SPAM:
1925 		case MATCHCRITERIA_HAS_ATTACHMENT:
1926 		case MATCHCRITERIA_HAS_NO_ATTACHMENT:
1927 		case MATCHCRITERIA_SIGNED:
1928 		case MATCHCRITERIA_NOT_SIGNED:
1929 		case MATCHCRITERIA_COLORLABEL:
1930 		case MATCHCRITERIA_NOT_COLORLABEL:
1931 		case MATCHCRITERIA_IGNORE_THREAD:
1932 		case MATCHCRITERIA_NOT_IGNORE_THREAD:
1933 		case MATCHCRITERIA_WATCH_THREAD:
1934 		case MATCHCRITERIA_NOT_WATCH_THREAD:
1935 		case MATCHCRITERIA_SUBJECT:
1936 		case MATCHCRITERIA_NOT_SUBJECT:
1937 		case MATCHCRITERIA_FROM:
1938 		case MATCHCRITERIA_NOT_FROM:
1939 		case MATCHCRITERIA_TO:
1940 		case MATCHCRITERIA_NOT_TO:
1941 		case MATCHCRITERIA_CC:
1942 		case MATCHCRITERIA_NOT_CC:
1943 		case MATCHCRITERIA_TO_OR_CC:
1944 		case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
1945 		case MATCHCRITERIA_TAG:
1946 		case MATCHCRITERIA_NOT_TAG:
1947 		case MATCHCRITERIA_TAGGED:
1948 		case MATCHCRITERIA_NOT_TAGGED:
1949 		case MATCHCRITERIA_AGE_GREATER:
1950 		case MATCHCRITERIA_AGE_LOWER:
1951 		case MATCHCRITERIA_AGE_GREATER_HOURS:
1952 		case MATCHCRITERIA_AGE_LOWER_HOURS:
1953 		case MATCHCRITERIA_DATE_AFTER:
1954 		case MATCHCRITERIA_DATE_BEFORE:
1955 		case MATCHCRITERIA_NEWSGROUPS:
1956 		case MATCHCRITERIA_NOT_NEWSGROUPS:
1957 		case MATCHCRITERIA_MESSAGEID:
1958 		case MATCHCRITERIA_NOT_MESSAGEID:
1959 		case MATCHCRITERIA_INREPLYTO:
1960 		case MATCHCRITERIA_NOT_INREPLYTO:
1961 		case MATCHCRITERIA_REFERENCES:
1962 		case MATCHCRITERIA_NOT_REFERENCES:
1963 		case MATCHCRITERIA_SCORE_GREATER:
1964 		case MATCHCRITERIA_SCORE_LOWER:
1965 		case MATCHCRITERIA_SCORE_EQUAL:
1966 		case MATCHCRITERIA_SIZE_GREATER:
1967 		case MATCHCRITERIA_SIZE_SMALLER:
1968 		case MATCHCRITERIA_SIZE_EQUAL:
1969 		case MATCHCRITERIA_TEST:
1970 		case MATCHCRITERIA_NOT_TEST:
1971 		case MATCHCRITERIA_PARTIAL:
1972 		case MATCHCRITERIA_NOT_PARTIAL:
1973 			if (matcherprop_match(matcher, info)) {
1974 				if (!matchers->bool_and) {
1975 					if (debug_filtering_session)
1976 						log_status_ok(LOG_DEBUG_FILTERING, _("message matches\n"));
1977 					return TRUE;
1978 				}
1979 			}
1980 			else {
1981 				if (matchers->bool_and) {
1982 					if (debug_filtering_session)
1983 						log_status_nok(LOG_DEBUG_FILTERING, _("message does not match\n"));
1984 					return FALSE;
1985 				}
1986 			}
1987 		}
1988 	}
1989 
1990 	/* test the condition on the file */
1991 
1992 	if (matcherlist_match_file(matchers, info, result)) {
1993 		if (!matchers->bool_and) {
1994 			if (debug_filtering_session)
1995 				log_status_ok(LOG_DEBUG_FILTERING, _("message matches\n"));
1996 			return TRUE;
1997 		}
1998 	} else {
1999 		if (matchers->bool_and) {
2000 			if (debug_filtering_session)
2001 				log_status_nok(LOG_DEBUG_FILTERING, _("message does not match\n"));
2002 			return FALSE;
2003 		}
2004 	}
2005 
2006 	if (debug_filtering_session) {
2007 		if (result)
2008 			log_status_ok(LOG_DEBUG_FILTERING, _("message matches\n"));
2009 		else
2010 			log_status_nok(LOG_DEBUG_FILTERING, _("message does not match\n"));
2011 	}
2012 	return result;
2013 }
2014 
2015 
quote_filter_str(gchar * result,guint size,const gchar * path)2016 static gint quote_filter_str(gchar * result, guint size,
2017 			     const gchar * path)
2018 {
2019 	const gchar * p;
2020 	gchar * result_p;
2021 	guint remaining;
2022 
2023 	result_p = result;
2024 	remaining = size;
2025 
2026 	for(p = path ; * p != '\0' ; p ++) {
2027 
2028 		if ((* p != '\"') && (* p != '\\')) {
2029 			if (remaining > 0) {
2030 				* result_p = * p;
2031 				result_p ++;
2032 				remaining --;
2033 			}
2034 			else {
2035 				result[size - 1] = '\0';
2036 				return -1;
2037 			}
2038 		}
2039 		else {
2040 			if (remaining >= 2) {
2041 				* result_p = '\\';
2042 				result_p ++;
2043 				* result_p = * p;
2044 				result_p ++;
2045 				remaining -= 2;
2046 			}
2047 			else {
2048 				result[size - 1] = '\0';
2049 				return -1;
2050 			}
2051 		}
2052 	}
2053 	if (remaining > 0) {
2054 		* result_p = '\0';
2055 	}
2056 	else {
2057 		result[size - 1] = '\0';
2058 		return -1;
2059 	}
2060 
2061 	return 0;
2062 }
2063 
2064 
matcher_quote_str(const gchar * src)2065 gchar * matcher_quote_str(const gchar * src)
2066 {
2067 	gchar * res;
2068 	gint len;
2069 
2070 	len = strlen(src) * 2 + 1;
2071 	res = g_malloc(len);
2072 	quote_filter_str(res, len, src);
2073 
2074 	return res;
2075 }
2076 
2077 /*!
2078  *\brief	Convert a matcher structure to a string
2079  *
2080  *\param	matcher Matcher structure
2081  *
2082  *\return	gchar * Newly allocated string
2083  */
matcherprop_to_string(MatcherProp * matcher)2084 gchar *matcherprop_to_string(MatcherProp *matcher)
2085 {
2086 	gchar *matcher_str = NULL;
2087 	const gchar *criteria_str;
2088 	const gchar *matchtype_str;
2089 	int i;
2090 	gchar * quoted_expr;
2091 	gchar * quoted_header;
2092 
2093 	criteria_str = NULL;
2094 	for (i = 0; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)); i++) {
2095 		if (matchparser_tab[i].id == matcher->criteria)
2096 			criteria_str = matchparser_tab[i].str;
2097 	}
2098 	if (criteria_str == NULL)
2099 		return NULL;
2100 
2101 	switch (matcher->criteria) {
2102 	case MATCHCRITERIA_AGE_GREATER:
2103 	case MATCHCRITERIA_AGE_LOWER:
2104 	case MATCHCRITERIA_AGE_GREATER_HOURS:
2105 	case MATCHCRITERIA_AGE_LOWER_HOURS:
2106 	case MATCHCRITERIA_SCORE_GREATER:
2107 	case MATCHCRITERIA_SCORE_LOWER:
2108 	case MATCHCRITERIA_SCORE_EQUAL:
2109 	case MATCHCRITERIA_SIZE_GREATER:
2110 	case MATCHCRITERIA_SIZE_SMALLER:
2111 	case MATCHCRITERIA_SIZE_EQUAL:
2112 	case MATCHCRITERIA_COLORLABEL:
2113 	case MATCHCRITERIA_NOT_COLORLABEL:
2114 		return g_strdup_printf("%s %i", criteria_str, matcher->value);
2115 	case MATCHCRITERIA_ALL:
2116 	case MATCHCRITERIA_UNREAD:
2117 	case MATCHCRITERIA_NOT_UNREAD:
2118 	case MATCHCRITERIA_NEW:
2119 	case MATCHCRITERIA_NOT_NEW:
2120 	case MATCHCRITERIA_MARKED:
2121 	case MATCHCRITERIA_NOT_MARKED:
2122 	case MATCHCRITERIA_DELETED:
2123 	case MATCHCRITERIA_NOT_DELETED:
2124 	case MATCHCRITERIA_REPLIED:
2125 	case MATCHCRITERIA_NOT_REPLIED:
2126 	case MATCHCRITERIA_FORWARDED:
2127 	case MATCHCRITERIA_NOT_FORWARDED:
2128 	case MATCHCRITERIA_LOCKED:
2129 	case MATCHCRITERIA_NOT_LOCKED:
2130 	case MATCHCRITERIA_SPAM:
2131 	case MATCHCRITERIA_NOT_SPAM:
2132 	case MATCHCRITERIA_HAS_ATTACHMENT:
2133 	case MATCHCRITERIA_HAS_NO_ATTACHMENT:
2134 	case MATCHCRITERIA_SIGNED:
2135 	case MATCHCRITERIA_NOT_SIGNED:
2136 	case MATCHCRITERIA_PARTIAL:
2137 	case MATCHCRITERIA_NOT_PARTIAL:
2138 	case MATCHCRITERIA_IGNORE_THREAD:
2139 	case MATCHCRITERIA_NOT_IGNORE_THREAD:
2140 	case MATCHCRITERIA_WATCH_THREAD:
2141 	case MATCHCRITERIA_NOT_WATCH_THREAD:
2142 	case MATCHCRITERIA_TAGGED:
2143 	case MATCHCRITERIA_NOT_TAGGED:
2144 		return g_strdup(criteria_str);
2145 	case MATCHCRITERIA_TEST:
2146 	case MATCHCRITERIA_NOT_TEST:
2147 	case MATCHCRITERIA_DATE_AFTER:
2148 	case MATCHCRITERIA_DATE_BEFORE:
2149 		quoted_expr = matcher_quote_str(matcher->expr);
2150 		matcher_str = g_strdup_printf("%s \"%s\"",
2151 					      criteria_str, quoted_expr);
2152 		g_free(quoted_expr);
2153                 return matcher_str;
2154 	case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK:
2155 	case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK:
2156 		quoted_header = matcher_quote_str(matcher->header);
2157 		quoted_expr = matcher_quote_str(matcher->expr);
2158 		matcher_str = g_strdup_printf("%s \"%s\" in \"%s\"",
2159 					      criteria_str, quoted_header, quoted_expr);
2160 		g_free(quoted_header);
2161 		g_free(quoted_expr);
2162 		return matcher_str;
2163 	}
2164 
2165 	matchtype_str = NULL;
2166 	for (i = 0; i < sizeof matchparser_tab / sizeof matchparser_tab[0]; i++) {
2167 		if (matchparser_tab[i].id == matcher->matchtype)
2168 			matchtype_str = matchparser_tab[i].str;
2169 	}
2170 
2171 	if (matchtype_str == NULL)
2172 		return NULL;
2173 
2174 	switch (matcher->matchtype) {
2175 	case MATCHTYPE_MATCH:
2176 	case MATCHTYPE_MATCHCASE:
2177 	case MATCHTYPE_REGEXP:
2178 	case MATCHTYPE_REGEXPCASE:
2179 		quoted_expr = matcher_quote_str(matcher->expr);
2180 		if (matcher->header) {
2181 			quoted_header = matcher_quote_str(matcher->header);
2182 			matcher_str = g_strdup_printf
2183 					("%s \"%s\" %s \"%s\"",
2184 					 criteria_str, quoted_header,
2185 					 matchtype_str, quoted_expr);
2186 			g_free(quoted_header);
2187 		}
2188 		else
2189 			matcher_str = g_strdup_printf
2190 					("%s %s \"%s\"", criteria_str,
2191 					 matchtype_str, quoted_expr);
2192                 g_free(quoted_expr);
2193 		break;
2194 	}
2195 
2196 	return matcher_str;
2197 }
2198 
2199 /*!
2200  *\brief	Convert a list of conditions to a string
2201  *
2202  *\param	matchers List of conditions
2203  *
2204  *\return	gchar * Newly allocated string
2205  */
matcherlist_to_string(const MatcherList * matchers)2206 gchar *matcherlist_to_string(const MatcherList *matchers)
2207 {
2208 	gint count;
2209 	gchar **vstr;
2210 	GSList *l;
2211 	gchar **cur_str;
2212 	gchar *result = NULL;
2213 
2214 	count = g_slist_length(matchers->matchers);
2215 	vstr = g_new(gchar *, count + 1);
2216 
2217 	for (l = matchers->matchers, cur_str = vstr; l != NULL;
2218 	     l = g_slist_next(l), cur_str ++) {
2219 		*cur_str = matcherprop_to_string((MatcherProp *) l->data);
2220 		if (*cur_str == NULL)
2221 			break;
2222 	}
2223 	*cur_str = NULL;
2224 
2225 	if (matchers->bool_and)
2226 		result = g_strjoinv(" & ", vstr);
2227 	else
2228 		result = g_strjoinv(" | ", vstr);
2229 
2230 	for (cur_str = vstr ; *cur_str != NULL ; cur_str ++)
2231 		g_free(*cur_str);
2232 	g_free(vstr);
2233 
2234 	return result;
2235 }
2236 
2237 
2238 #define STRLEN_ZERO(s) ((s) ? strlen(s) : 0)
2239 #define STRLEN_DEFAULT(s,d) ((s) ? strlen(s) : STRLEN_ZERO(d))
2240 
add_str_default(gchar ** dest,const gchar * s,const gchar * d)2241 static void add_str_default(gchar ** dest,
2242 			    const gchar * s, const gchar * d)
2243 {
2244 	gchar quoted_str[4096];
2245 	const gchar * str;
2246 
2247         if (s != NULL)
2248 		str = s;
2249 	else
2250 		str = d;
2251 
2252 	quote_cmd_argument(quoted_str, sizeof(quoted_str), str);
2253 	strcpy(* dest, quoted_str);
2254 
2255 	(* dest) += strlen(* dest);
2256 }
2257 
2258 /* matching_build_command() - preferably cmd should be unescaped */
2259 /*!
2260  *\brief	Build the command-line to execute
2261  *
2262  *\param	cmd String with command-line specifiers
2263  *\param	info Message info to use for command
2264  *
2265  *\return	gchar * Newly allocated string
2266  */
matching_build_command(const gchar * cmd,MsgInfo * info)2267 gchar *matching_build_command(const gchar *cmd, MsgInfo *info)
2268 {
2269 	const gchar *s = cmd;
2270 	gchar *filename = NULL;
2271 	gchar *processed_cmd;
2272 	gchar *p;
2273 	gint size;
2274 
2275 	const gchar *const no_subject    = _("(none)") ;
2276 	const gchar *const no_from       = _("(none)") ;
2277 	const gchar *const no_to         = _("(none)") ;
2278 	const gchar *const no_cc         = _("(none)") ;
2279 	const gchar *const no_date       = _("(none)") ;
2280 	const gchar *const no_msgid      = _("(none)") ;
2281 	const gchar *const no_newsgroups = _("(none)") ;
2282 	const gchar *const no_references = _("(none)") ;
2283 
2284 	size = STRLEN_ZERO(cmd) + 1;
2285 	while (*s != '\0') {
2286 		if (*s == '%') {
2287 			s++;
2288 			switch (*s) {
2289 			case '%':
2290 				size -= 1;
2291 				break;
2292 			case 's': /* subject */
2293 				size += STRLEN_DEFAULT(info->subject, no_subject) - 2;
2294 				break;
2295 			case 'f': /* from */
2296 				size += STRLEN_DEFAULT(info->from, no_from) - 2;
2297 				break;
2298 			case 't': /* to */
2299 				size += STRLEN_DEFAULT(info->to, no_to) - 2;
2300 				break;
2301 			case 'c': /* cc */
2302 				size += STRLEN_DEFAULT(info->cc, no_cc) - 2;
2303 				break;
2304 			case 'd': /* date */
2305 				size += STRLEN_DEFAULT(info->date, no_date) - 2;
2306 				break;
2307 			case 'i': /* message-id */
2308 				size += STRLEN_DEFAULT(info->msgid, no_msgid) - 2;
2309 				break;
2310 			case 'n': /* newsgroups */
2311 				size += STRLEN_DEFAULT(info->newsgroups, no_newsgroups) - 2;
2312 				break;
2313 			case 'r': /* references */
2314                                 /* FIXME: using the inreplyto header for reference */
2315 				size += STRLEN_DEFAULT(info->inreplyto, no_references) - 2;
2316 				break;
2317 			case 'F': /* file */
2318 				if (filename == NULL)
2319 					filename = folder_item_fetch_msg(info->folder, info->msgnum);
2320 
2321 				if (filename == NULL) {
2322 					g_warning("filename is not set");
2323 					return NULL;
2324 				}
2325 				else {
2326 					size += strlen(filename) - 2;
2327 				}
2328 				break;
2329 			}
2330 			s++;
2331 		}
2332 		else s++;
2333 	}
2334 
2335 	/* as the string can be quoted, we double the result */
2336 	size *= 2;
2337 
2338 	processed_cmd = g_new0(gchar, size);
2339 	s = cmd;
2340 	p = processed_cmd;
2341 
2342 	while (*s != '\0') {
2343 		if (*s == '%') {
2344 			s++;
2345 			switch (*s) {
2346 			case '%':
2347 				*p = '%';
2348 				p++;
2349 				break;
2350 			case 's': /* subject */
2351 				add_str_default(&p, info->subject,
2352 						no_subject);
2353 				break;
2354 			case 'f': /* from */
2355 				add_str_default(&p, info->from,
2356 						no_from);
2357 				break;
2358 			case 't': /* to */
2359 				add_str_default(&p, info->to,
2360 						no_to);
2361 				break;
2362 			case 'c': /* cc */
2363 				add_str_default(&p, info->cc,
2364 						no_cc);
2365 				break;
2366 			case 'd': /* date */
2367 				add_str_default(&p, info->date,
2368 						no_date);
2369 				break;
2370 			case 'i': /* message-id */
2371 				add_str_default(&p, info->msgid,
2372 						no_msgid);
2373 				break;
2374 			case 'n': /* newsgroups */
2375 				add_str_default(&p, info->newsgroups,
2376 						no_newsgroups);
2377 				break;
2378 			case 'r': /* references */
2379                                 /* FIXME: using the inreplyto header for references */
2380 				add_str_default(&p, info->inreplyto, no_references);
2381 				break;
2382 			case 'F': /* file */
2383 				if (filename != NULL)
2384 					add_str_default(&p, filename, NULL);
2385 				break;
2386 			default:
2387 				*p = '%';
2388 				p++;
2389 				*p = *s;
2390 				p++;
2391 				break;
2392 			}
2393 			s++;
2394 		}
2395 		else {
2396 			*p = *s;
2397 			p++;
2398 			s++;
2399 		}
2400 	}
2401 	g_free(filename);
2402 
2403 	return processed_cmd;
2404 }
2405 #undef STRLEN_DEFAULT
2406 #undef STRLEN_ZERO
2407 
2408 /* ************************************************************ */
2409 
2410 
2411 /*!
2412  *\brief	Write filtering list to file
2413  *
2414  *\param	fp File
2415  *\param	prefs_filtering List of filtering conditions
2416  */
prefs_filtering_write(FILE * fp,GSList * prefs_filtering)2417 static int prefs_filtering_write(FILE *fp, GSList *prefs_filtering)
2418 {
2419 	GSList *cur = NULL;
2420 
2421 	for (cur = prefs_filtering; cur != NULL; cur = cur->next) {
2422 		gchar *filtering_str = NULL;
2423 		gchar *tmp_name = NULL;
2424 		FilteringProp *prop = NULL;
2425 
2426 		if (NULL == (prop = (FilteringProp *) cur->data))
2427 			continue;
2428 
2429 		if (NULL == (filtering_str = filteringprop_to_string(prop)))
2430 			continue;
2431 
2432 		if (prop->enabled) {
2433 			if (claws_fputs("enabled ", fp) == EOF) {
2434 				FILE_OP_ERROR("filtering config", "claws_fputs");
2435 				return -1;
2436 			}
2437 		} else {
2438 			if (claws_fputs("disabled ", fp) == EOF) {
2439 				FILE_OP_ERROR("filtering config", "claws_fputs");
2440 				return -1;
2441 			}
2442 		}
2443 
2444 		if (claws_fputs("rulename \"", fp) == EOF) {
2445 			FILE_OP_ERROR("filtering config", "claws_fputs");
2446 			g_free(filtering_str);
2447 			return -1;
2448 		}
2449 		tmp_name = prop->name;
2450 		while (tmp_name && *tmp_name != '\0') {
2451 			if (*tmp_name != '"') {
2452 				if (claws_fputc(*tmp_name, fp) == EOF) {
2453 					FILE_OP_ERROR("filtering config", "claws_fputs || claws_fputc");
2454 					g_free(filtering_str);
2455 					return -1;
2456 				}
2457 			} else if (*tmp_name == '"') {
2458 				if (claws_fputc('\\', fp) == EOF ||
2459 				    claws_fputc('"', fp) == EOF) {
2460 					FILE_OP_ERROR("filtering config", "claws_fputs || claws_fputc");
2461 					g_free(filtering_str);
2462 					return -1;
2463 				}
2464 			}
2465 			tmp_name ++;
2466 		}
2467 		if (claws_fputs("\" ", fp) == EOF) {
2468 			FILE_OP_ERROR("filtering config", "claws_fputs");
2469 			g_free(filtering_str);
2470 			return -1;
2471 		}
2472 
2473 		if (prop->account_id != 0) {
2474 			gchar *tmp = NULL;
2475 
2476 			tmp = g_strdup_printf("account %d ", prop->account_id);
2477 			if (claws_fputs(tmp, fp) == EOF) {
2478 				FILE_OP_ERROR("filtering config", "claws_fputs");
2479 				g_free(tmp);
2480 				return -1;
2481 			}
2482 			g_free(tmp);
2483 		}
2484 
2485 		if(claws_fputs(filtering_str, fp) == EOF ||
2486 		    claws_fputc('\n', fp) == EOF) {
2487 			FILE_OP_ERROR("filtering config", "claws_fputs || claws_fputc");
2488 			g_free(filtering_str);
2489 			return -1;
2490 		}
2491 		g_free(filtering_str);
2492 	}
2493 
2494 	return 0;
2495 }
2496 
2497 typedef struct _NodeLoopData {
2498 	FILE *fp;
2499 	gboolean error;
2500 } NodeLoopData;
2501 
2502 /*!
2503  *\brief	Write matchers from a folder item
2504  *
2505  *\param	node Node with folder info
2506  *\param	data File pointer
2507  *
2508  *\return	gboolean FALSE
2509  */
prefs_matcher_write_func(GNode * node,gpointer d)2510 static gboolean prefs_matcher_write_func(GNode *node, gpointer d)
2511 {
2512 	FolderItem *item;
2513 	NodeLoopData *data = (NodeLoopData *)d;
2514 	gchar *id;
2515 	GSList *prefs_filtering;
2516 
2517         item = node->data;
2518         /* prevent warning */
2519         if (item->path == NULL)
2520                 return FALSE;
2521         id = folder_item_get_identifier(item);
2522         if (id == NULL)
2523                 return FALSE;
2524         prefs_filtering = item->prefs->processing;
2525 
2526 	if (prefs_filtering != NULL) {
2527 		if (fprintf(data->fp, "[%s]\n", id) < 0) {
2528 			data->error = TRUE;
2529 			goto fail;
2530 		}
2531 		if (prefs_filtering_write(data->fp, prefs_filtering) < 0) {
2532 			data->error = TRUE;
2533 			goto fail;
2534 		}
2535 		if (claws_fputc('\n', data->fp) == EOF) {
2536 			data->error = TRUE;
2537 			goto fail;
2538 		}
2539 	}
2540 fail:
2541 	g_free(id);
2542 
2543 	return FALSE;
2544 }
2545 
2546 /*!
2547  *\brief	Save matchers from folder items
2548  *
2549  *\param	fp File
2550  */
prefs_matcher_save(FILE * fp)2551 static int prefs_matcher_save(FILE *fp)
2552 {
2553 	GList *cur;
2554 	NodeLoopData data;
2555 
2556 	data.fp = fp;
2557 	data.error = FALSE;
2558 
2559 	for (cur = folder_get_list() ; cur != NULL ; cur = g_list_next(cur)) {
2560 		Folder *folder;
2561 
2562 		folder = (Folder *) cur->data;
2563 		g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
2564 				prefs_matcher_write_func, &data);
2565 	}
2566 
2567 	if (data.error == TRUE)
2568 		return -1;
2569 
2570         /* pre global rules */
2571         if (fprintf(fp, "[preglobal]\n") < 0 ||
2572             prefs_filtering_write(fp, pre_global_processing) < 0 ||
2573             claws_fputc('\n', fp) == EOF)
2574 		return -1;
2575 
2576         /* post global rules */
2577         if (fprintf(fp, "[postglobal]\n") < 0 ||
2578             prefs_filtering_write(fp, post_global_processing) < 0 ||
2579             claws_fputc('\n', fp) == EOF)
2580 		return -1;
2581 
2582         /* filtering rules */
2583 	if (fprintf(fp, "[filtering]\n") < 0 ||
2584             prefs_filtering_write(fp, filtering_rules) < 0 ||
2585             claws_fputc('\n', fp) == EOF)
2586 		return -1;
2587 
2588 	return 0;
2589 }
2590 
2591 /*!
2592  *\brief	Write filtering / matcher configuration file
2593  */
prefs_matcher_write_config(void)2594 void prefs_matcher_write_config(void)
2595 {
2596 	gchar *rcpath;
2597 	PrefFile *pfile;
2598 
2599 	debug_print("Writing matcher configuration...\n");
2600 
2601 	rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2602 			     MATCHER_RC, NULL);
2603 
2604 	if ((pfile = prefs_write_open(rcpath)) == NULL) {
2605 		g_warning("failed to write configuration to file");
2606 		g_free(rcpath);
2607 		return;
2608 	}
2609 
2610 	g_free(rcpath);
2611 
2612 	if (prefs_matcher_save(pfile->fp) < 0) {
2613 		g_warning("failed to write configuration to file");
2614 		prefs_file_close_revert(pfile);
2615 	} else if (prefs_file_close(pfile) < 0) {
2616 		g_warning("failed to save configuration to file");
2617 	}
2618 }
2619 
2620 /*!
2621  *\brief	Read matcher configuration
2622  */
prefs_matcher_read_config(void)2623 void prefs_matcher_read_config(void)
2624 {
2625 	gchar *rcpath;
2626 	FILE *f;
2627 
2628 	create_matchparser_hashtab();
2629 	prefs_filtering_clear();
2630 
2631 	rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, MATCHER_RC, NULL);
2632 
2633 	f = claws_fopen(rcpath, "rb");
2634 	g_free(rcpath);
2635 
2636 	if (f != NULL) {
2637 		matcher_parser_start_parsing(f);
2638 		claws_fclose(matcher_parserin);
2639 	}
2640 }
2641