1 /*
2  * LibSylph -- E-Mail client library
3  * Copyright (C) 1999-2015 Hiroyuki Yamamoto
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library 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 GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23 
24 #include "defs.h"
25 
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <string.h>
29 #include <strings.h>
30 #include <stdlib.h>
31 #include <sys/types.h>
32 #if USE_ONIGURUMA
33 #  include <oniguruma.h>
34 #elif HAVE_REGEX_H
35 #  include <regex.h>
36 #endif
37 #include <time.h>
38 
39 #include "filter.h"
40 #include "procmsg.h"
41 #include "procheader.h"
42 #include "folder.h"
43 #include "utils.h"
44 #include "xml.h"
45 #include "prefs.h"
46 #include "prefs_common.h"
47 #include "prefs_account.h"
48 #include "account.h"
49 
50 typedef enum
51 {
52 	FLT_O_CONTAIN	= 1 << 0,
53 	FLT_O_CASE_SENS	= 1 << 1,
54 	FLT_O_REGEX	= 1 << 2
55 } FilterOldFlag;
56 
57 static FilterInAddressBookFunc default_addrbook_func = NULL;
58 
59 static gboolean filter_match_cond	(FilterCond	*cond,
60 					 MsgInfo	*msginfo,
61 					 GSList		*hlist,
62 					 FilterInfo	*fltinfo);
63 static gboolean filter_match_header_cond(FilterCond	*cond,
64 					 GSList		*hlist);
65 static gboolean filter_match_in_addressbook
66 					(FilterCond	*cond,
67 					 GSList		*hlist,
68 					 FilterInfo	*fltinfo);
69 
70 static void filter_cond_free		(FilterCond	*cond);
71 static void filter_action_free		(FilterAction	*action);
72 
73 
filter_apply(GSList * fltlist,const gchar * file,FilterInfo * fltinfo)74 gint filter_apply(GSList *fltlist, const gchar *file, FilterInfo *fltinfo)
75 {
76 	MsgInfo *msginfo;
77 	gint ret = 0;
78 
79 	g_return_val_if_fail(file != NULL, -1);
80 	g_return_val_if_fail(fltinfo != NULL, -1);
81 
82 	if (!fltlist) return 0;
83 
84 	msginfo = procheader_parse_file(file, fltinfo->flags, FALSE);
85 	if (!msginfo) return 0;
86 	msginfo->file_path = g_strdup(file);
87 
88 	/* inherit MIME flag */
89 	fltinfo->flags.tmp_flags =
90 		(fltinfo->flags.tmp_flags & ~MSG_CACHED_FLAG_MASK) |
91 		(msginfo->flags.tmp_flags & MSG_CACHED_FLAG_MASK);
92 
93 	ret = filter_apply_msginfo(fltlist, msginfo, fltinfo);
94 
95 	procmsg_msginfo_free(msginfo);
96 
97 	return ret;
98 }
99 
filter_apply_msginfo(GSList * fltlist,MsgInfo * msginfo,FilterInfo * fltinfo)100 gint filter_apply_msginfo(GSList *fltlist, MsgInfo *msginfo,
101 			  FilterInfo *fltinfo)
102 {
103 	gchar *file;
104 	GSList *hlist, *cur;
105 	FilterRule *rule;
106 	gint ret = 0;
107 
108 	g_return_val_if_fail(msginfo != NULL, -1);
109 	g_return_val_if_fail(fltinfo != NULL, -1);
110 
111 	fltinfo->error = FLT_ERROR_OK;
112 
113 	if (!fltlist) return 0;
114 
115 	file = procmsg_get_message_file(msginfo);
116 	if (!file)
117 		return -1;
118 	hlist = procheader_get_header_list_from_file(file);
119 	if (!hlist) {
120 		g_free(file);
121 		return 0;
122 	}
123 
124 	procmsg_set_auto_decrypt_message(FALSE);
125 
126 	for (cur = fltlist; cur != NULL; cur = cur->next) {
127 		gboolean matched;
128 
129 		rule = (FilterRule *)cur->data;
130 		if (!rule->enabled) continue;
131 		matched = filter_match_rule(rule, msginfo, hlist, fltinfo);
132 		if (fltinfo->error != FLT_ERROR_OK) {
133 			g_warning("filter_match_rule() returned error (code: %d)\n", fltinfo->error);
134 		}
135 		if (matched) {
136 			debug_print("filter-log: %s: rule [%s] matched\n",
137 				    G_STRFUNC, rule->name ? rule->name : "(No name)");
138 			ret = filter_action_exec(rule, msginfo, file, fltinfo);
139 			if (ret < 0) {
140 				g_warning("filter_action_exec() returned error (code: %d)\n", fltinfo->error);
141 				break;
142 			}
143 			if (fltinfo->drop_done == TRUE ||
144 			    fltinfo->actions[FLT_ACTION_STOP_EVAL] == TRUE)
145 				break;
146 		}
147 	}
148 
149 	procmsg_set_auto_decrypt_message(TRUE);
150 
151 	procheader_header_list_destroy(hlist);
152 	g_free(file);
153 
154 	return ret;
155 }
156 
filter_action_exec(FilterRule * rule,MsgInfo * msginfo,const gchar * file,FilterInfo * fltinfo)157 gint filter_action_exec(FilterRule *rule, MsgInfo *msginfo, const gchar *file,
158 			FilterInfo *fltinfo)
159 {
160 	FolderItem *dest_folder = NULL;
161 	FilterAction *action;
162 	GSList *cur;
163 	gchar *cmdline;
164 	gboolean copy_to_self = FALSE;
165 	gint ret;
166 
167 	g_return_val_if_fail(rule != NULL, -1);
168 	g_return_val_if_fail(msginfo != NULL, -1);
169 	g_return_val_if_fail(file != NULL, -1);
170 	g_return_val_if_fail(fltinfo != NULL, -1);
171 
172 	for (cur = rule->action_list; cur != NULL; cur = cur->next) {
173 		action = (FilterAction *)cur->data;
174 
175 		switch (action->type) {
176 		case FLT_ACTION_MARK:
177 			debug_print("filter_action_exec(): mark\n");
178 			MSG_SET_PERM_FLAGS(fltinfo->flags, MSG_MARKED);
179 			fltinfo->actions[action->type] = TRUE;
180 			break;
181 		case FLT_ACTION_COLOR_LABEL:
182 			debug_print("filter_action_exec(): color label: %d\n",
183 				    action->int_value);
184 			MSG_UNSET_PERM_FLAGS(fltinfo->flags,
185 					     MSG_CLABEL_FLAG_MASK);
186 			MSG_SET_COLORLABEL_VALUE(fltinfo->flags,
187 						 action->int_value);
188 			fltinfo->actions[action->type] = TRUE;
189 			break;
190 		case FLT_ACTION_MARK_READ:
191 			debug_print("filter_action_exec(): mark as read\n");
192 			if (msginfo->folder) {
193 				if (MSG_IS_NEW(fltinfo->flags))
194 					msginfo->folder->new--;
195 				if (MSG_IS_UNREAD(fltinfo->flags))
196 					msginfo->folder->unread--;
197 			}
198 			MSG_UNSET_PERM_FLAGS(fltinfo->flags, MSG_NEW|MSG_UNREAD);
199 			fltinfo->actions[action->type] = TRUE;
200 			break;
201 		case FLT_ACTION_EXEC:
202 			cmdline = g_strconcat(action->str_value, " \"", file,
203 					      "\"", NULL);
204 			ret = execute_command_line(cmdline, FALSE);
205 			fltinfo->last_exec_exit_status = ret;
206 			if (ret == -1) {
207 				fltinfo->error = FLT_ERROR_EXEC_FAILED;
208 				g_warning("filter_action_exec: cannot execute command: %s", cmdline);
209 				g_free(cmdline);
210 				return -1;
211 			}
212 			g_free(cmdline);
213 			fltinfo->actions[action->type] = TRUE;
214 			break;
215 		case FLT_ACTION_EXEC_ASYNC:
216 			cmdline = g_strconcat(action->str_value, " \"", file,
217 					      "\"", NULL);
218 			ret = execute_command_line(cmdline, TRUE);
219 			fltinfo->last_exec_exit_status = ret;
220 			if (ret == -1) {
221 				fltinfo->error = FLT_ERROR_EXEC_FAILED;
222 				g_warning("filter_action_exec: cannot execute command: %s", cmdline);
223 				g_free(cmdline);
224 				return -1;
225 			}
226 			g_free(cmdline);
227 			fltinfo->actions[action->type] = TRUE;
228 			break;
229 		default:
230 			break;
231 		}
232 	}
233 
234 	for (cur = rule->action_list; cur != NULL; cur = cur->next) {
235 		action = (FilterAction *)cur->data;
236 
237 		switch (action->type) {
238 		case FLT_ACTION_MOVE:
239 		case FLT_ACTION_COPY:
240 			dest_folder = folder_find_item_from_identifier
241 				(action->str_value);
242 			if (!dest_folder) {
243 				g_warning("dest folder '%s' not found\n",
244 					  action->str_value);
245 				fltinfo->error = FLT_ERROR_ERROR;
246 				return -1;
247 			}
248 			debug_print("filter_action_exec(): %s: dest_folder = %s\n",
249 				    action->type == FLT_ACTION_COPY ?
250 				    "copy" : "move", action->str_value);
251 
252 			if (msginfo->folder) {
253 				gint val;
254 
255 				/* local filtering */
256 				if (msginfo->folder == dest_folder)
257 					copy_to_self = TRUE;
258 				else {
259 					if (action->type == FLT_ACTION_COPY) {
260 						MsgFlags save_flags;
261 
262 						save_flags = msginfo->flags;
263 						msginfo->flags = fltinfo->flags;
264 						val = folder_item_copy_msg
265 							(dest_folder, msginfo);
266 						msginfo->flags = save_flags;
267 						if (val == -1) {
268 							fltinfo->error = FLT_ERROR_ERROR;
269 							return -1;
270 						}
271 					}
272 					fltinfo->actions[action->type] = TRUE;
273 				}
274 			} else {
275 				MsgFlags save_flags;
276 
277 				save_flags = msginfo->flags;
278 				msginfo->flags = fltinfo->flags;
279 				if (folder_item_add_msg_msginfo
280 					(dest_folder, msginfo, FALSE) < 0) {
281 					msginfo->flags = save_flags;
282 					fltinfo->error = FLT_ERROR_ERROR;
283 					return -1;
284 				}
285 				msginfo->flags = save_flags;
286 				fltinfo->actions[action->type] = TRUE;
287 			}
288 
289 			fltinfo->dest_list = g_slist_append(fltinfo->dest_list,
290 							    dest_folder);
291 			if (action->type == FLT_ACTION_MOVE) {
292 				fltinfo->move_dest = dest_folder;
293 				fltinfo->drop_done = TRUE;
294 			}
295 			break;
296 		default:
297 			break;
298 		}
299 	}
300 
301 	if (fltinfo->drop_done == TRUE)
302 		return 0;
303 
304 	for (cur = rule->action_list; cur != NULL; cur = cur->next) {
305 		action = (FilterAction *)cur->data;
306 
307 		switch (action->type) {
308 		case FLT_ACTION_NOT_RECEIVE:
309 			debug_print("filter_action_exec(): don't receive\n");
310 			fltinfo->drop_done = TRUE;
311 			fltinfo->actions[action->type] = TRUE;
312 			return 0;
313 		case FLT_ACTION_DELETE:
314 			debug_print("filter_action_exec(): delete\n");
315 			if (msginfo->folder) {
316 				/* local filtering */
317 				if (copy_to_self == FALSE)
318 					fltinfo->actions[action->type] = TRUE;
319 			} else
320 				fltinfo->actions[action->type] = TRUE;
321 
322 			fltinfo->drop_done = TRUE;
323 			return 0;
324 		case FLT_ACTION_STOP_EVAL:
325 			debug_print("filter_action_exec(): stop evaluation\n");
326 			fltinfo->actions[action->type] = TRUE;
327 			return 0;
328 		default:
329 			break;
330 		}
331 	}
332 
333 	return 0;
334 }
335 
strmatch_regex(const gchar * haystack,const gchar * needle)336 static gboolean strmatch_regex(const gchar *haystack, const gchar *needle)
337 {
338 #ifdef USE_ONIGURUMA
339 	gint ret = 0;
340 	OnigRegex reg;
341 	OnigErrorInfo err_info;
342 	const UChar *ptn = (const UChar *)needle;
343 	const UChar *str = (const UChar *)haystack;
344 	size_t haystack_len;
345 
346 	ret = onig_new(&reg, ptn, ptn + strlen(needle),
347 		       /* ONIG_OPTION_EXTEND requires spaces to be escaped */
348 		       /* ONIG_OPTION_IGNORECASE|ONIG_OPTION_EXTEND, */
349 		       ONIG_OPTION_IGNORECASE,
350 		       ONIG_ENCODING_UTF8, ONIG_SYNTAX_POSIX_EXTENDED,
351 		       &err_info);
352 	if (ret != ONIG_NORMAL) {
353 		g_warning("strmatch_regex: onig_new() failed: %d", ret);
354 		return FALSE;
355 	}
356 
357 	haystack_len = strlen(haystack);
358 	ret = onig_search(reg, str, str + haystack_len,
359 			  str, str + haystack_len, NULL, 0);
360 	onig_free(reg);
361 
362 	if (ret >= 0)
363 		return TRUE;
364 	else
365 		return FALSE;
366 #elif defined(HAVE_REGCOMP)
367 	gint ret = 0;
368 	regex_t preg;
369 
370 	ret = regcomp(&preg, needle, REG_ICASE|REG_EXTENDED);
371 	if (ret != 0)
372 		return FALSE;
373 
374 	ret = regexec(&preg, haystack, 0, NULL, 0);
375 	regfree(&preg);
376 
377 	if (ret == 0)
378 		return TRUE;
379 	else
380 		return FALSE;
381 #else
382 	return FALSE;
383 #endif
384 }
385 
filter_match_rule(FilterRule * rule,MsgInfo * msginfo,GSList * hlist,FilterInfo * fltinfo)386 gboolean filter_match_rule(FilterRule *rule, MsgInfo *msginfo, GSList *hlist,
387 			   FilterInfo *fltinfo)
388 {
389 	FilterCond *cond;
390 	GSList *cur;
391 	gboolean matched;
392 
393 	g_return_val_if_fail(rule->cond_list != NULL, FALSE);
394 
395 	switch (rule->timing) {
396 	case FLT_TIMING_ANY:
397 		break;
398 	case FLT_TIMING_ON_RECEIVE:
399 		if (msginfo->folder != NULL)
400 			return FALSE;
401 		break;
402 	case FLT_TIMING_MANUAL:
403 		if (msginfo->folder == NULL)
404 			return FALSE;
405 		break;
406 	default:
407 		break;
408 	}
409 
410 	if (rule->bool_op == FLT_AND) {
411 		for (cur = rule->cond_list; cur != NULL; cur = cur->next) {
412 			cond = (FilterCond *)cur->data;
413 			if (cond->type >= FLT_COND_SIZE_GREATER) {
414 				matched = filter_match_cond
415 					(cond, msginfo, hlist, fltinfo);
416 				if (matched == FALSE)
417 					return FALSE;
418 			}
419 		}
420 
421 		for (cur = rule->cond_list; cur != NULL; cur = cur->next) {
422 			cond = (FilterCond *)cur->data;
423 			if (cond->type <= FLT_COND_TO_OR_CC) {
424 				matched = filter_match_cond
425 					(cond, msginfo, hlist, fltinfo);
426 				if (matched == FALSE)
427 					return FALSE;
428 			}
429 		}
430 
431 		for (cur = rule->cond_list; cur != NULL; cur = cur->next) {
432 			cond = (FilterCond *)cur->data;
433 			if (cond->type == FLT_COND_BODY ||
434 			    cond->type == FLT_COND_CMD_TEST) {
435 				matched = filter_match_cond
436 					(cond, msginfo, hlist, fltinfo);
437 				if (matched == FALSE)
438 					return FALSE;
439 			}
440 		}
441 
442 		return TRUE;
443 	} else if (rule->bool_op == FLT_OR) {
444 		for (cur = rule->cond_list; cur != NULL; cur = cur->next) {
445 			cond = (FilterCond *)cur->data;
446 			if (cond->type >= FLT_COND_SIZE_GREATER) {
447 				matched = filter_match_cond
448 					(cond, msginfo, hlist, fltinfo);
449 				if (matched == TRUE)
450 					return TRUE;
451 			}
452 		}
453 
454 		for (cur = rule->cond_list; cur != NULL; cur = cur->next) {
455 			cond = (FilterCond *)cur->data;
456 			if (cond->type <= FLT_COND_TO_OR_CC) {
457 				matched = filter_match_cond
458 					(cond, msginfo, hlist, fltinfo);
459 				if (matched == TRUE)
460 					return TRUE;
461 			}
462 		}
463 
464 		for (cur = rule->cond_list; cur != NULL; cur = cur->next) {
465 			cond = (FilterCond *)cur->data;
466 			if (cond->type == FLT_COND_BODY ||
467 			    cond->type == FLT_COND_CMD_TEST) {
468 				matched = filter_match_cond
469 					(cond, msginfo, hlist, fltinfo);
470 				if (matched == TRUE)
471 					return TRUE;
472 			}
473 		}
474 
475 		return FALSE;
476 	}
477 
478 	return FALSE;
479 }
480 
filter_match_cond(FilterCond * cond,MsgInfo * msginfo,GSList * hlist,FilterInfo * fltinfo)481 static gboolean filter_match_cond(FilterCond *cond, MsgInfo *msginfo,
482 				  GSList *hlist, FilterInfo *fltinfo)
483 {
484 	gint ret;
485 	gboolean matched = FALSE;
486 	gboolean not_match = FALSE;
487 	gint64 timediff = 0;
488 	gchar *file;
489 	gchar *cmdline;
490 	PrefsAccount *cond_ac;
491 
492 	switch (cond->type) {
493 	case FLT_COND_HEADER:
494 		if (cond->match_type == FLT_IN_ADDRESSBOOK)
495 			return filter_match_in_addressbook(cond, hlist, fltinfo);
496 		else
497 			return filter_match_header_cond(cond, hlist);
498 	case FLT_COND_ANY_HEADER:
499 		return filter_match_header_cond(cond, hlist);
500 	case FLT_COND_TO_OR_CC:
501 		if (cond->match_type == FLT_IN_ADDRESSBOOK)
502 			return filter_match_in_addressbook(cond, hlist, fltinfo);
503 		else
504 			return filter_match_header_cond(cond, hlist);
505 	case FLT_COND_BODY:
506 		matched = procmime_find_string(msginfo, cond->str_value,
507 					       cond->match_func);
508 		break;
509 	case FLT_COND_CMD_TEST:
510 		file = procmsg_get_message_file(msginfo);
511 		if (!file)
512 			return FALSE;
513 		cmdline = g_strconcat(cond->str_value, " \"", file, "\"", NULL);
514 		ret = execute_command_line_async_wait(cmdline);
515 		fltinfo->last_exec_exit_status = ret;
516 		matched = (ret == 0);
517 		if (ret == -1)
518 			fltinfo->error = FLT_ERROR_EXEC_FAILED;
519 		g_free(cmdline);
520 		g_free(file);
521 		break;
522 	case FLT_COND_SIZE_GREATER:
523 		matched = (msginfo->size > cond->int_value * 1024);
524 		break;
525 	case FLT_COND_AGE_GREATER:
526 		timediff = time(NULL) - msginfo->date_t;
527 		matched = (timediff > cond->int_value * 24 * 60 * 60);
528 		break;
529 	case FLT_COND_UNREAD:
530 		matched = MSG_IS_UNREAD(msginfo->flags);
531 		break;
532 	case FLT_COND_MARK:
533 		matched = MSG_IS_MARKED(msginfo->flags);
534 		break;
535 	case FLT_COND_COLOR_LABEL:
536 		matched = (MSG_GET_COLORLABEL_VALUE(msginfo->flags) != 0);
537 		break;
538 	case FLT_COND_MIME:
539 		matched = MSG_IS_MIME(msginfo->flags);
540 		break;
541 	case FLT_COND_ACCOUNT:
542 		cond_ac = account_find_from_id(cond->int_value);
543 		matched = (cond_ac != NULL && cond_ac == fltinfo->account);
544 		break;
545 	default:
546 		g_warning("filter_match_cond(): unknown condition: %d\n",
547 			  cond->type);
548 		fltinfo->error = FLT_ERROR_ERROR;
549 		return FALSE;
550 	}
551 
552 	if (FLT_IS_NOT_MATCH(cond->match_flag)) {
553 		not_match = TRUE;
554 		matched = !matched;
555 	}
556 
557 	if (matched && get_debug_mode()) {
558 		gchar *sv = cond->str_value ? cond->str_value : "";
559 		gchar *nm = not_match ? " (reverse match)" : "";
560 
561 		switch (cond->type) {
562 		case FLT_COND_BODY:
563 			debug_print("filter-log: %s: BODY, str_value: [%s]%s\n", G_STRFUNC, sv, nm);
564 			break;
565 		case FLT_COND_CMD_TEST:
566 			debug_print("filter-log: %s: CMD_TEST, str_value: [%s]%s\n", G_STRFUNC, sv, nm);
567 			break;
568 		case FLT_COND_SIZE_GREATER:
569 			debug_print("filter-log: %s: SIZE_GREATER: %u %s %d (KB)%s\n", G_STRFUNC, msginfo->size, not_match ? "<=" : ">", cond->int_value, nm);
570 			break;
571 		case FLT_COND_AGE_GREATER:
572 			debug_print("filter-log: %s: AGE_GREATER: %lld (sec) %s %d (day)%s\n", G_STRFUNC, timediff, not_match ? "<=" : ">", cond->int_value, nm);
573 			break;
574 		case FLT_COND_UNREAD:
575 			debug_print("filter-log: %s: UNREAD%s\n", G_STRFUNC, nm);
576 			break;
577 		case FLT_COND_MARK:
578 			debug_print("filter-log: %s: MARK%s\n", G_STRFUNC, nm);
579 			break;
580 		case FLT_COND_COLOR_LABEL:
581 			debug_print("filter-log: %s: COLOR_LABEL%s\n", G_STRFUNC, nm);
582 			break;
583 		case FLT_COND_MIME:
584 			debug_print("filter-log: %s: MIME%s\n", G_STRFUNC, nm);
585 			break;
586 		case FLT_COND_ACCOUNT:
587 			debug_print("filter-log: %s: ACCOUNT [%d]%s\n", G_STRFUNC, cond->int_value, nm);
588 			break;
589 		default:
590 			break;
591 		}
592 	}
593 
594 	return matched;
595 }
596 
filter_match_header_cond(FilterCond * cond,GSList * hlist)597 static gboolean filter_match_header_cond(FilterCond *cond, GSList *hlist)
598 {
599 	gboolean matched = FALSE;
600 	gboolean not_match = FALSE;
601 	GSList *cur;
602 	Header *header;
603 
604 	for (cur = hlist; cur != NULL; cur = cur->next) {
605 		header = (Header *)cur->data;
606 
607 		switch (cond->type) {
608 		case FLT_COND_HEADER:
609 			if (!g_ascii_strcasecmp
610 				(header->name, cond->header_name)) {
611 				if (!cond->str_value ||
612 				    cond->match_func(header->body,
613 						     cond->str_value))
614 					matched = TRUE;
615 			}
616 			break;
617 		case FLT_COND_ANY_HEADER:
618 			if (!cond->str_value ||
619 			    cond->match_func(header->body, cond->str_value))
620 				matched = TRUE;
621 			break;
622 		case FLT_COND_TO_OR_CC:
623 			if (!g_ascii_strcasecmp(header->name, "To") ||
624 			    !g_ascii_strcasecmp(header->name, "Cc")) {
625 				if (!cond->str_value ||
626 				    cond->match_func(header->body,
627 						     cond->str_value))
628 					matched = TRUE;
629 			}
630 			break;
631 		default:
632 			break;
633 		}
634 
635 		if (matched == TRUE)
636 			break;
637 	}
638 
639 	if (FLT_IS_NOT_MATCH(cond->match_flag)) {
640 		not_match = TRUE;
641 		matched = !matched;
642 	}
643 
644 	if (matched && get_debug_mode()) {
645 		gchar *sv = cond->str_value ? cond->str_value : "";
646 		gchar *nm = not_match ? " (reverse match)" : "";
647 
648 		switch (cond->type) {
649 		case FLT_COND_HEADER:
650 			debug_print("filter-log: %s: HEADER [%s], str_value: [%s]%s\n", G_STRFUNC, cond->header_name, sv, nm);
651 			break;
652 		case FLT_COND_ANY_HEADER:
653 			debug_print("filter-log: %s: ANY_HEADER, str_value: [%s]%s\n", G_STRFUNC, sv, nm);
654 			break;
655 		case FLT_COND_TO_OR_CC:
656 			debug_print("filter-log: %s: TO_OR_CC, str_value: [%s]%s\n", G_STRFUNC, sv, nm);
657 			break;
658 		default:
659 			break;
660 		}
661 	}
662 
663 	return matched;
664 }
665 
filter_match_in_addressbook(FilterCond * cond,GSList * hlist,FilterInfo * fltinfo)666 static gboolean filter_match_in_addressbook(FilterCond *cond, GSList *hlist,
667 					    FilterInfo *fltinfo)
668 {
669 	gboolean matched = FALSE;
670 	gboolean not_match = FALSE;
671 	GSList *cur;
672 	Header *header;
673 
674 	if (!default_addrbook_func)
675 		return FALSE;
676 	if (cond->type != FLT_COND_HEADER && cond->type != FLT_COND_TO_OR_CC)
677 		return FALSE;
678 
679 	for (cur = hlist; cur != NULL; cur = cur->next) {
680 		header = (Header *)cur->data;
681 
682 		if (cond->type == FLT_COND_HEADER) {
683 			if (!g_ascii_strcasecmp
684 				(header->name, cond->header_name)) {
685 				if (default_addrbook_func(header->body))
686 					matched = TRUE;
687 			}
688 		} else if (cond->type == FLT_COND_TO_OR_CC) {
689 			if (!g_ascii_strcasecmp(header->name, "To") ||
690 			    !g_ascii_strcasecmp(header->name, "Cc")) {
691 				if (default_addrbook_func(header->body))
692 					matched = TRUE;
693 			}
694 		}
695 
696 		if (matched == TRUE)
697 			break;
698 	}
699 
700 	if (FLT_IS_NOT_MATCH(cond->match_flag)) {
701 		not_match = TRUE;
702 		matched = !matched;
703 	}
704 
705 	if (matched && get_debug_mode()) {
706 		gchar *nm = not_match ? " (reverse match)" : "";
707 
708 		switch (cond->type) {
709 		case FLT_COND_HEADER:
710 			debug_print("filter-log: %s: HEADER [%s], IN_ADDRESSBOOK%s\n", G_STRFUNC, cond->header_name, nm);
711 			break;
712 		case FLT_COND_TO_OR_CC:
713 			debug_print("filter-log: %s: TO_OR_CC, IN_ADDRESSBOOK%s\n", G_STRFUNC, nm);
714 			break;
715 		default:
716 			break;
717 		}
718 	}
719 
720 	return matched;
721 }
722 
filter_rule_requires_full_headers(FilterRule * rule)723 gboolean filter_rule_requires_full_headers(FilterRule *rule)
724 {
725 	GSList *cur;
726 
727 	for (cur = rule->cond_list; cur != NULL; cur = cur->next) {
728 		FilterCond *cond = (FilterCond *)cur->data;
729 		const gchar *name = cond->header_name;
730 
731 		if (cond->type == FLT_COND_HEADER && name) {
732 			if (g_ascii_strcasecmp(name, "Date") != 0 &&
733 			    g_ascii_strcasecmp(name, "From") != 0 &&
734 			    g_ascii_strcasecmp(name, "To") != 0 &&
735 			    g_ascii_strcasecmp(name, "Newsgroups") != 0 &&
736 			    g_ascii_strcasecmp(name, "Subject") != 0)
737 				return TRUE;
738 		} else if (cond->type == FLT_COND_ANY_HEADER ||
739 			   cond->type == FLT_COND_TO_OR_CC)
740 			return TRUE;
741 	}
742 
743 	return FALSE;
744 }
745 
746 #define RETURN_IF_TAG_NOT_MATCH(tag_name)			\
747 	if (strcmp2(xmlnode->tag->tag, tag_name) != 0) {	\
748 		g_warning("tag name != \"" tag_name "\"\n");	\
749 		filter_cond_list_free(cond_list);		\
750 		filter_action_list_free(cond_list);		\
751 		return FALSE;					\
752 	}							\
753 
754 #define STR_SWITCH(sw)		{ const gchar *sw_str = sw;
755 #define STR_CASE_BEGIN(str)	if (!strcmp(sw_str, str)) {
756 #define STR_CASE(str)		} else if (!strcmp(sw_str, str)) {
757 #define STR_CASE_END		} }
758 
filter_xml_node_func(GNode * node,gpointer data)759 static gboolean filter_xml_node_func(GNode *node, gpointer data)
760 {
761 	XMLNode *xmlnode;
762 	GList *list;
763 	const gchar *name = NULL;
764 	FilterTiming timing = FLT_TIMING_ANY;
765 	gboolean enabled = TRUE;
766 	FilterRule *rule;
767 	FilterBoolOp bool_op = FLT_OR;
768 	const gchar *target_folder = NULL;
769 	gboolean recursive = FALSE;
770 	GSList *cond_list = NULL;
771 	GSList *action_list = NULL;
772 	GNode *child, *cond_child, *action_child;
773 	GSList **fltlist = (GSList **)data;
774 
775 	if (g_node_depth(node) != 2) return FALSE;
776 	g_return_val_if_fail(node->data != NULL, FALSE);
777 
778 	xmlnode = node->data;
779 	RETURN_IF_TAG_NOT_MATCH("rule");
780 
781 	for (list = xmlnode->tag->attr; list != NULL; list = list->next) {
782 		XMLAttr *attr = (XMLAttr *)list->data;
783 
784 		if (!attr || !attr->name || !attr->value) continue;
785 		if (!strcmp(attr->name, "name"))
786 			name = attr->value;
787 		else if (!strcmp(attr->name, "timing")) {
788 			if (!strcmp(attr->value, "any"))
789 				timing = FLT_TIMING_ANY;
790 			else if (!strcmp(attr->value, "receive"))
791 				timing = FLT_TIMING_ON_RECEIVE;
792 			else if (!strcmp(attr->value, "manual"))
793 				timing = FLT_TIMING_MANUAL;
794 		} else if (!strcmp(attr->name, "enabled")) {
795 			if (!strcmp(attr->value, "true"))
796 				enabled = TRUE;
797 			else
798 				enabled = FALSE;
799 		}
800 	}
801 
802 	/* condition list */
803 	child = node->children;
804 	if (!child) {
805 		g_warning("<rule> must have children\n");
806 		return FALSE;
807 	}
808 	xmlnode = child->data;
809 	RETURN_IF_TAG_NOT_MATCH("condition-list");
810 	for (list = xmlnode->tag->attr; list != NULL; list = list->next) {
811 		XMLAttr *attr = (XMLAttr *)list->data;
812 
813 		if (attr && attr->name && attr->value &&
814 		    !strcmp(attr->name, "bool")) {
815 			if (!strcmp(attr->value, "or"))
816 				bool_op = FLT_OR;
817 			else
818 				bool_op = FLT_AND;
819 		}
820 	}
821 
822 	for (cond_child = child->children; cond_child != NULL;
823 	     cond_child = cond_child->next) {
824 		const gchar *type = NULL;
825 		const gchar *name = NULL;
826 		const gchar *value = NULL;
827 		gboolean case_sens = FALSE;
828 		FilterCond *cond;
829 		FilterCondType cond_type = FLT_COND_HEADER;
830 		FilterMatchType match_type = FLT_CONTAIN;
831 		FilterMatchFlag match_flag = 0;
832 
833 		xmlnode = cond_child->data;
834 		if (!xmlnode || !xmlnode->tag || !xmlnode->tag->tag) continue;
835 
836 		for (list = xmlnode->tag->attr; list != NULL; list = list->next) {
837 			XMLAttr *attr = (XMLAttr *)list->data;
838 
839 			if (!attr || !attr->name || !attr->value) continue;
840 
841 			STR_SWITCH(attr->name)
842 			STR_CASE_BEGIN("type")
843 				type = attr->value;
844 			STR_CASE("name")
845 				name = attr->value;
846 			STR_CASE("case")
847 				case_sens = TRUE;
848 			STR_CASE("recursive")
849 				if (!strcmp(attr->value, "true"))
850 					recursive = TRUE;
851 				else
852 					recursive = FALSE;
853 			STR_CASE_END
854 		}
855 
856 		if (type) {
857 			filter_rule_match_type_str_to_enum
858 				(type, &match_type, &match_flag);
859 		}
860 		if (case_sens)
861 			match_flag |= FLT_CASE_SENS;
862 		value = xmlnode->element;
863 
864 		STR_SWITCH(xmlnode->tag->tag)
865 		STR_CASE_BEGIN("match-header")
866 			cond_type = FLT_COND_HEADER;
867 		STR_CASE("match-any-header")
868 			cond_type = FLT_COND_ANY_HEADER;
869 		STR_CASE("match-to-or-cc")
870 			cond_type = FLT_COND_TO_OR_CC;
871 		STR_CASE("match-body-text")
872 			cond_type = FLT_COND_BODY;
873 		STR_CASE("command-test")
874 			cond_type = FLT_COND_CMD_TEST;
875 		STR_CASE("size")
876 			cond_type = FLT_COND_SIZE_GREATER;
877 		STR_CASE("age")
878 			cond_type = FLT_COND_AGE_GREATER;
879 		STR_CASE("unread")
880 			cond_type = FLT_COND_UNREAD;
881 		STR_CASE("mark")
882 			cond_type = FLT_COND_MARK;
883 		STR_CASE("color-label")
884 			cond_type = FLT_COND_COLOR_LABEL;
885 		STR_CASE("mime")
886 			cond_type = FLT_COND_MIME;
887 		STR_CASE("account-id")
888 			cond_type = FLT_COND_ACCOUNT;
889 		STR_CASE("target-folder")
890 			target_folder = value;
891 			continue;
892 		STR_CASE_END
893 
894 		cond = filter_cond_new(cond_type, match_type, match_flag,
895 				       name, value);
896 		cond_list = g_slist_append(cond_list, cond);
897 	}
898 
899 	/* action list */
900 	child = child->next;
901 	if (!child) {
902 		g_warning("<rule> must have multiple children\n");
903 		filter_cond_list_free(cond_list);
904 		return FALSE;
905 	}
906 	xmlnode = child->data;
907 	RETURN_IF_TAG_NOT_MATCH("action-list");
908 
909 	for (action_child = child->children; action_child != NULL;
910 	     action_child = action_child->next) {
911 		FilterAction *action;
912 		FilterActionType action_type = FLT_ACTION_NONE;
913 
914 		xmlnode = action_child->data;
915 		if (!xmlnode || !xmlnode->tag || !xmlnode->tag->tag) continue;
916 
917 		STR_SWITCH(xmlnode->tag->tag)
918 		STR_CASE_BEGIN("move")
919 			action_type = FLT_ACTION_MOVE;
920 		STR_CASE("copy")
921 			action_type = FLT_ACTION_COPY;
922 		STR_CASE("not-receive")
923 			action_type = FLT_ACTION_NOT_RECEIVE;
924 		STR_CASE("delete")
925 			action_type = FLT_ACTION_DELETE;
926 		STR_CASE("exec")
927 			action_type = FLT_ACTION_EXEC;
928 		STR_CASE("exec-async")
929 			action_type = FLT_ACTION_EXEC_ASYNC;
930 		STR_CASE("mark")
931 			action_type = FLT_ACTION_MARK;
932 		STR_CASE("color-label")
933 			action_type = FLT_ACTION_COLOR_LABEL;
934 		STR_CASE("mark-as-read")
935 			action_type = FLT_ACTION_MARK_READ;
936 		STR_CASE("forward")
937 			action_type = FLT_ACTION_FORWARD;
938 		STR_CASE("forward-as-attachment")
939 			action_type = FLT_ACTION_FORWARD_AS_ATTACHMENT;
940 		STR_CASE("redirect")
941 			action_type = FLT_ACTION_REDIRECT;
942 		STR_CASE("stop-eval")
943 			action_type = FLT_ACTION_STOP_EVAL;
944 		STR_CASE_END
945 
946 		action = filter_action_new(action_type, xmlnode->element);
947 		action_list = g_slist_append(action_list, action);
948 	}
949 
950 	if (name && cond_list) {
951 		rule = filter_rule_new(name, bool_op, cond_list, action_list);
952 		rule->timing = timing;
953 		rule->enabled = enabled;
954 		if (target_folder) {
955 			rule->target_folder = g_strdup(target_folder);
956 			rule->recursive = recursive;
957 		}
958 		*fltlist = g_slist_prepend(*fltlist, rule);
959 	}
960 
961 	return FALSE;
962 }
963 
964 #undef RETURN_IF_TAG_NOT_MATCH
965 #undef STR_SWITCH
966 #undef STR_CASE_BEGIN
967 #undef STR_CASE
968 #undef STR_CASE_END
969 
filter_xml_node_to_filter_list(GNode * node)970 GSList *filter_xml_node_to_filter_list(GNode *node)
971 {
972 	GSList *fltlist = NULL;
973 
974 	g_return_val_if_fail(node != NULL, NULL);
975 
976 	g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, 2,
977 			filter_xml_node_func, &fltlist);
978 	fltlist = g_slist_reverse(fltlist);
979 
980 	return fltlist;
981 }
982 
filter_read_file(const gchar * file)983 GSList *filter_read_file(const gchar *file)
984 {
985 	GNode *node;
986 	GSList *list;
987 
988 	g_return_val_if_fail(file != NULL, NULL);
989 
990 	debug_print("Reading %s\n", file);
991 
992 	if (!is_file_exist(file))
993 		return NULL;
994 
995 	node = xml_parse_file(file);
996 	if (!node) {
997 		g_warning("Can't parse %s\n", file);
998 		return NULL;
999 	}
1000 
1001 	list = filter_xml_node_to_filter_list(node);
1002 
1003 	xml_free_tree(node);
1004 
1005 	return list;
1006 }
1007 
filter_read_config(void)1008 void filter_read_config(void)
1009 {
1010 	gchar *rcpath;
1011 
1012 	debug_print("Reading filter configuration...\n");
1013 
1014 	/* remove all previous filter list */
1015 	while (prefs_common.fltlist != NULL) {
1016 		FilterRule *rule = (FilterRule *)prefs_common.fltlist->data;
1017 
1018 		filter_rule_free(rule);
1019 		prefs_common.fltlist = g_slist_remove(prefs_common.fltlist,
1020 						      rule);
1021 	}
1022 
1023 	rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FILTER_LIST,
1024 			     NULL);
1025 	prefs_common.fltlist = filter_read_file(rcpath);
1026 	g_free(rcpath);
1027 }
1028 
1029 #define NODE_NEW(tag, text) \
1030 	node = xml_node_new(xml_tag_new(tag), text)
1031 #define ADD_ATTR(name, value) \
1032 	xml_tag_add_attr(node->tag, xml_attr_new(name, value))
1033 
filter_write_file(GSList * list,const gchar * file)1034 void filter_write_file(GSList *list, const gchar *file)
1035 {
1036 	PrefFile *pfile;
1037 	GSList *cur;
1038 
1039 	g_return_if_fail(file != NULL);
1040 
1041 	if ((pfile = prefs_file_open(file)) == NULL) {
1042 		g_warning("failed to write filter configuration to file: %s\n",
1043 			  file);
1044 		return;
1045 	}
1046 
1047 	xml_file_put_xml_decl(pfile->fp);
1048 	fputs("\n<filter>\n", pfile->fp);
1049 
1050 	for (cur = list; cur != NULL; cur = cur->next) {
1051 		FilterRule *rule = (FilterRule *)cur->data;
1052 		GSList *cur_cond;
1053 		GSList *cur_action;
1054 		gchar match_type[64];
1055 		gchar nstr[16];
1056 
1057 		fputs("    <rule name=\"", pfile->fp);
1058 		xml_file_put_escape_str(pfile->fp, rule->name);
1059 		fprintf(pfile->fp, "\" timing=\"%s\"",
1060 			rule->timing == FLT_TIMING_ON_RECEIVE ? "receive" :
1061 			rule->timing == FLT_TIMING_MANUAL ? "manual" : "any");
1062 		fprintf(pfile->fp, " enabled=\"%s\">\n",
1063 			rule->enabled ? "true" : "false");
1064 
1065 		fprintf(pfile->fp, "        <condition-list bool=\"%s\">\n",
1066 			rule->bool_op == FLT_OR ? "or" : "and");
1067 
1068 		for (cur_cond = rule->cond_list; cur_cond != NULL;
1069 		     cur_cond = cur_cond->next) {
1070 			FilterCond *cond = (FilterCond *)cur_cond->data;
1071 			XMLNode *node = NULL;
1072 
1073 			switch (cond->match_type) {
1074 			case FLT_CONTAIN:
1075 				strncpy2(match_type,
1076 					 FLT_IS_NOT_MATCH(cond->match_flag)
1077 					 ? "not-contain" : "contains",
1078 					 sizeof(match_type));
1079 				break;
1080 			case FLT_EQUAL:
1081 				strncpy2(match_type,
1082 					 FLT_IS_NOT_MATCH(cond->match_flag)
1083 					 ? "is-not" : "is", sizeof(match_type));
1084 				break;
1085 			case FLT_REGEX:
1086 				strncpy2(match_type,
1087 					 FLT_IS_NOT_MATCH(cond->match_flag)
1088 					 ? "not-regex" : "regex",
1089 					 sizeof(match_type));
1090 				break;
1091 			case FLT_IN_ADDRESSBOOK:
1092 				strncpy2(match_type,
1093 					 FLT_IS_NOT_MATCH(cond->match_flag)
1094 					 ? "not-in-addressbook" : "in-addressbook",
1095 					 sizeof(match_type));
1096 				break;
1097 			default:
1098 				match_type[0] = '\0';
1099 				break;
1100 			}
1101 
1102 			switch (cond->type) {
1103 			case FLT_COND_HEADER:
1104 				NODE_NEW("match-header", cond->str_value);
1105 				ADD_ATTR("type", match_type);
1106 				ADD_ATTR("name", cond->header_name);
1107 				if (FLT_IS_CASE_SENS(cond->match_flag))
1108 					ADD_ATTR("case", "true");
1109 				break;
1110 			case FLT_COND_ANY_HEADER:
1111 				NODE_NEW("match-any-header", cond->str_value);
1112 				ADD_ATTR("type", match_type);
1113 				if (FLT_IS_CASE_SENS(cond->match_flag))
1114 					ADD_ATTR("case", "true");
1115 				break;
1116 			case FLT_COND_TO_OR_CC:
1117 				NODE_NEW("match-to-or-cc", cond->str_value);
1118 				ADD_ATTR("type", match_type);
1119 				if (FLT_IS_CASE_SENS(cond->match_flag))
1120 					ADD_ATTR("case", "true");
1121 				break;
1122 			case FLT_COND_BODY:
1123 				NODE_NEW("match-body-text", cond->str_value);
1124 				ADD_ATTR("type", match_type);
1125 				if (FLT_IS_CASE_SENS(cond->match_flag))
1126 					ADD_ATTR("case", "true");
1127 				break;
1128 			case FLT_COND_CMD_TEST:
1129 				NODE_NEW("command-test", cond->str_value);
1130 				break;
1131 			case FLT_COND_SIZE_GREATER:
1132 				NODE_NEW("size", itos_buf(nstr, cond->int_value));
1133 				ADD_ATTR("type",
1134 					 FLT_IS_NOT_MATCH(cond->match_flag)
1135 					 ? "lt" : "gt");
1136 				break;
1137 			case FLT_COND_AGE_GREATER:
1138 				NODE_NEW("age", itos_buf(nstr, cond->int_value));
1139 				ADD_ATTR("type",
1140 					 FLT_IS_NOT_MATCH(cond->match_flag)
1141 					 ? "lt" : "gt");
1142 				break;
1143 			case FLT_COND_UNREAD:
1144 				NODE_NEW("unread", NULL);
1145 				ADD_ATTR("type",
1146 					 FLT_IS_NOT_MATCH(cond->match_flag)
1147 					 ? "is-not" : "is");
1148 				break;
1149 			case FLT_COND_MARK:
1150 				NODE_NEW("mark", NULL);
1151 				ADD_ATTR("type",
1152 					 FLT_IS_NOT_MATCH(cond->match_flag)
1153 					 ? "is-not" : "is");
1154 				break;
1155 			case FLT_COND_COLOR_LABEL:
1156 				NODE_NEW("color-label", NULL);
1157 				ADD_ATTR("type",
1158 					 FLT_IS_NOT_MATCH(cond->match_flag)
1159 					 ? "is-not" : "is");
1160 				break;
1161 			case FLT_COND_MIME:
1162 				NODE_NEW("mime", NULL);
1163 				ADD_ATTR("type",
1164 					 FLT_IS_NOT_MATCH(cond->match_flag)
1165 					 ? "is-not" : "is");
1166 				break;
1167 			case FLT_COND_ACCOUNT:
1168 				 NODE_NEW("account-id", itos_buf(nstr, cond->int_value));
1169 				 break;
1170 			default:
1171 				 break;
1172 			}
1173 
1174 			if (node) {
1175 				fputs("            ", pfile->fp);
1176 				xml_file_put_node(pfile->fp, node);
1177 				xml_free_node(node);
1178 			}
1179 		}
1180 
1181 		if (rule->target_folder) {
1182 			XMLNode *node;
1183 
1184 			NODE_NEW("target-folder", rule->target_folder);
1185 			ADD_ATTR("recursive", rule->recursive
1186 				 ? "true" : "false");
1187 			fputs("            ", pfile->fp);
1188 			xml_file_put_node(pfile->fp, node);
1189 			xml_free_node(node);
1190 		}
1191 
1192 		fputs("        </condition-list>\n", pfile->fp);
1193 
1194 		fputs("        <action-list>\n", pfile->fp);
1195 
1196 		for (cur_action = rule->action_list; cur_action != NULL;
1197 		     cur_action = cur_action->next) {
1198 			FilterAction *action = (FilterAction *)cur_action->data;
1199 			XMLNode *node = NULL;
1200 
1201 			switch (action->type) {
1202 			case FLT_ACTION_MOVE:
1203 				NODE_NEW("move", action->str_value);
1204 				break;
1205 			case FLT_ACTION_COPY:
1206 				NODE_NEW("copy", action->str_value);
1207 				break;
1208 			case FLT_ACTION_NOT_RECEIVE:
1209 				NODE_NEW("not-receive", NULL);
1210 				break;
1211 			case FLT_ACTION_DELETE:
1212 				NODE_NEW("delete", NULL);
1213 				break;
1214 			case FLT_ACTION_EXEC:
1215 				NODE_NEW("exec", action->str_value);
1216 				break;
1217 			case FLT_ACTION_EXEC_ASYNC:
1218 				NODE_NEW("exec-async", action->str_value);
1219 				break;
1220 			case FLT_ACTION_MARK:
1221 				NODE_NEW("mark", NULL);
1222 				break;
1223 			case FLT_ACTION_COLOR_LABEL:
1224 				NODE_NEW("color-label", action->str_value);
1225 				break;
1226 			case FLT_ACTION_MARK_READ:
1227 				NODE_NEW("mark-as-read", NULL);
1228 				break;
1229 			case FLT_ACTION_FORWARD:
1230 				NODE_NEW("forward", action->str_value);
1231 				break;
1232 			case FLT_ACTION_FORWARD_AS_ATTACHMENT:
1233 				NODE_NEW("forward-as-attachment",
1234 					 action->str_value);
1235 				break;
1236 			case FLT_ACTION_REDIRECT:
1237 				NODE_NEW("redirect", action->str_value);
1238 				break;
1239 			case FLT_ACTION_STOP_EVAL:
1240 				NODE_NEW("stop-eval", NULL);
1241 				break;
1242 			default:
1243 				break;
1244 			}
1245 
1246 			if (node) {
1247 				fputs("            ", pfile->fp);
1248 				xml_file_put_node(pfile->fp, node);
1249 				xml_free_node(node);
1250 			}
1251 		}
1252 
1253 		fputs("        </action-list>\n", pfile->fp);
1254 
1255 		fputs("    </rule>\n", pfile->fp);
1256 	}
1257 
1258 	fputs("</filter>\n", pfile->fp);
1259 
1260 	if (prefs_file_close(pfile) < 0) {
1261 		g_warning("failed to write filter configuration to file: %s\n",
1262 			  file);
1263 		return;
1264 	}
1265 }
1266 
filter_write_config(void)1267 void filter_write_config(void)
1268 {
1269 	gchar *rcpath;
1270 
1271 	debug_print("Writing filter configuration...\n");
1272 
1273 	rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, FILTER_LIST,
1274 			     NULL);
1275 	filter_write_file(prefs_common.fltlist, rcpath);
1276 	g_free(rcpath);
1277 }
1278 
1279 #undef NODE_NEW
1280 #undef ADD_ATTR
1281 
filter_get_str(FilterRule * rule)1282 gchar *filter_get_str(FilterRule *rule)
1283 {
1284 	gchar *str;
1285 	FilterCond *cond1, *cond2;
1286 	FilterAction *action = NULL;
1287 	GSList *cur;
1288 	gint flag1 = 0, flag2 = 0;
1289 
1290 	cond1 = (FilterCond *)rule->cond_list->data;
1291 	if (rule->cond_list->next)
1292 		cond2 = (FilterCond *)rule->cond_list->next->data;
1293 	else
1294 		cond2 = NULL;
1295 
1296 	/* new -> old flag conversion */
1297 	switch (cond1->match_type) {
1298 	case FLT_CONTAIN:
1299 	case FLT_EQUAL:
1300 		flag1 = FLT_IS_NOT_MATCH(cond1->match_flag) ? 0 : FLT_O_CONTAIN;
1301 		if (FLT_IS_CASE_SENS(cond1->match_flag))
1302 			flag1 |= FLT_O_CASE_SENS;
1303 		break;
1304 	case FLT_REGEX:
1305 		flag1 = FLT_O_REGEX; break;
1306 	default:
1307 		break;
1308 	}
1309 	if (cond2) {
1310 		switch (cond2->match_type) {
1311 		case FLT_CONTAIN:
1312 		case FLT_EQUAL:
1313 			flag2 = FLT_IS_NOT_MATCH(cond2->match_flag) ? 0 : FLT_O_CONTAIN;
1314 			if (FLT_IS_CASE_SENS(cond2->match_flag))
1315 				flag2 |= FLT_O_CASE_SENS;
1316 			break;
1317 		case FLT_REGEX:
1318 			flag2 = FLT_O_REGEX; break;
1319 		default:
1320 			break;
1321 		}
1322 	} else
1323 		flag2 = FLT_O_CONTAIN;
1324 
1325 	for (cur = rule->action_list; cur != NULL; cur = cur->next) {
1326 		action = (FilterAction *)cur->data;
1327 		if (action->type == FLT_ACTION_MOVE ||
1328 		    action->type == FLT_ACTION_NOT_RECEIVE ||
1329 		    action->type == FLT_ACTION_DELETE)
1330 			break;
1331 	}
1332 
1333 	str = g_strdup_printf
1334 		("%s:%s:%c:%s:%s:%s:%d:%d:%c",
1335 		 cond1->header_name, cond1->str_value ? cond1->str_value : "",
1336 		 (cond2 && cond2->header_name) ?
1337 		 	(rule->bool_op == FLT_AND ? '&' : '|') : ' ',
1338 		 (cond2 && cond2->header_name) ? cond2->header_name : "",
1339 		 (cond2 && cond2->str_value) ? cond2->str_value : "",
1340 		 (action && action->str_value) ? action->str_value : "",
1341 		 flag1, flag2,
1342 		 (action && action->type == FLT_ACTION_MOVE) ? 'm' :
1343 		 (action && action->type == FLT_ACTION_NOT_RECEIVE) ? 'n' :
1344 		 (action && action->type == FLT_ACTION_DELETE) ? 'd' : ' ');
1345 
1346 	return str;
1347 }
1348 
1349 #define PARSE_ONE_PARAM(p, srcp) \
1350 { \
1351 	p = strchr(srcp, '\t'); \
1352 	if (!p) return NULL; \
1353 	else \
1354 		*p++ = '\0'; \
1355 }
1356 
filter_read_str(const gchar * str)1357 FilterRule *filter_read_str(const gchar *str)
1358 {
1359 	FilterRule *rule;
1360 	FilterBoolOp bool_op;
1361 	gint flag;
1362 	FilterCond *cond;
1363 	FilterMatchType match_type;
1364 	FilterMatchFlag match_flag;
1365 	FilterAction *action;
1366 	GSList *cond_list = NULL;
1367 	GSList *action_list = NULL;
1368 	gchar *tmp;
1369 	gchar *rule_name;
1370 	gchar *name1, *body1, *op, *name2, *body2, *dest;
1371 	gchar *flag1 = NULL, *flag2 = NULL, *action1 = NULL;
1372 
1373 	Xstrdup_a(tmp, str, return NULL);
1374 
1375 	name1 = tmp;
1376 	PARSE_ONE_PARAM(body1, name1);
1377 	PARSE_ONE_PARAM(op, body1);
1378 	PARSE_ONE_PARAM(name2, op);
1379 	PARSE_ONE_PARAM(body2, name2);
1380 	PARSE_ONE_PARAM(dest, body2);
1381 	if (strchr(dest, '\t')) {
1382 		gchar *p;
1383 
1384 		PARSE_ONE_PARAM(flag1, dest);
1385 		PARSE_ONE_PARAM(flag2, flag1);
1386 		PARSE_ONE_PARAM(action1, flag2);
1387 		if ((p = strchr(action1, '\t'))) *p = '\0';
1388 	}
1389 
1390 	bool_op = (*op == '&') ? FLT_AND : FLT_OR;
1391 
1392 	if (*name1) {
1393 		if (flag1)
1394 			flag = (FilterOldFlag)strtoul(flag1, NULL, 10);
1395 		else
1396 			flag = FLT_O_CONTAIN;
1397 		match_type = FLT_CONTAIN;
1398 		match_flag = 0;
1399 		if (flag & FLT_O_REGEX)
1400 			match_type = FLT_REGEX;
1401 		else if (!(flag & FLT_O_CONTAIN))
1402 			match_flag = FLT_NOT_MATCH;
1403 		if (flag & FLT_O_CASE_SENS)
1404 			match_flag |= FLT_CASE_SENS;
1405 		cond = filter_cond_new(FLT_COND_HEADER, match_type, match_flag,
1406 				       name1, body1);
1407 		cond_list = g_slist_append(cond_list, cond);
1408 	}
1409 	if (*name2) {
1410 		if (flag2)
1411 			flag = (FilterOldFlag)strtoul(flag2, NULL, 10);
1412 		else
1413 			flag = FLT_O_CONTAIN;
1414 		match_type = FLT_CONTAIN;
1415 		match_flag = 0;
1416 		if (flag & FLT_O_REGEX)
1417 			match_type = FLT_REGEX;
1418 		else if (!(flag & FLT_O_CONTAIN))
1419 			match_flag = FLT_NOT_MATCH;
1420 		if (flag & FLT_O_CASE_SENS)
1421 			match_flag |= FLT_CASE_SENS;
1422 		cond = filter_cond_new(FLT_COND_HEADER, match_type, match_flag,
1423 				       name2, body2);
1424 		cond_list = g_slist_append(cond_list, cond);
1425 	}
1426 
1427 	action = filter_action_new(FLT_ACTION_MOVE,
1428 				   *dest ? g_strdup(dest) : NULL);
1429 	if (action1) {
1430 		switch (*action1) {
1431 		case 'm': action->type = FLT_ACTION_MOVE;		break;
1432 		case 'n': action->type = FLT_ACTION_NOT_RECEIVE;	break;
1433 		case 'd': action->type = FLT_ACTION_DELETE;		break;
1434 		default:  g_warning("Invalid action: `%c'\n", *action1);
1435 		}
1436 	}
1437 	action_list = g_slist_append(action_list, action);
1438 
1439 	Xstrdup_a(rule_name, str, return NULL);
1440 	subst_char(rule_name, '\t', ':');
1441 
1442 	rule = filter_rule_new(rule_name, bool_op, cond_list, action_list);
1443 
1444 	return rule;
1445 }
1446 
filter_set_addressbook_func(FilterInAddressBookFunc func)1447 void filter_set_addressbook_func(FilterInAddressBookFunc func)
1448 {
1449 	default_addrbook_func = func;
1450 }
1451 
filter_get_addressbook_func(void)1452 FilterInAddressBookFunc filter_get_addressbook_func(void)
1453 {
1454 	return default_addrbook_func;
1455 }
1456 
filter_rule_new(const gchar * name,FilterBoolOp bool_op,GSList * cond_list,GSList * action_list)1457 FilterRule *filter_rule_new(const gchar *name, FilterBoolOp bool_op,
1458 			    GSList *cond_list, GSList *action_list)
1459 {
1460 	FilterRule *rule;
1461 
1462 	rule = g_new0(FilterRule, 1);
1463 	rule->name = g_strdup(name);
1464 	rule->bool_op = bool_op;
1465 	rule->cond_list = cond_list;
1466 	rule->action_list = action_list;
1467 	rule->timing = FLT_TIMING_ANY;
1468 	rule->enabled = TRUE;
1469 
1470 	return rule;
1471 }
1472 
filter_cond_new(FilterCondType type,FilterMatchType match_type,FilterMatchFlag match_flag,const gchar * header,const gchar * value)1473 FilterCond *filter_cond_new(FilterCondType type,
1474 			    FilterMatchType match_type,
1475 			    FilterMatchFlag match_flag,
1476 			    const gchar *header, const gchar *value)
1477 {
1478 	FilterCond *cond;
1479 
1480 	cond = g_new0(FilterCond, 1);
1481 	cond->type = type;
1482 	cond->match_type = match_type;
1483 	cond->match_flag = match_flag;
1484 
1485 	if (type == FLT_COND_HEADER)
1486 		cond->header_name =
1487 			(header && *header) ? g_strdup(header) : NULL;
1488 	else
1489 		cond->header_name = NULL;
1490 
1491 	cond->str_value = (value && *value) ? g_strdup(value) : NULL;
1492 	if (type == FLT_COND_SIZE_GREATER || type == FLT_COND_AGE_GREATER ||
1493 	    type == FLT_COND_ACCOUNT)
1494 		cond->int_value = atoi(value);
1495 	else
1496 		cond->int_value = 0;
1497 
1498 	if (match_type == FLT_REGEX)
1499 		cond->match_func = strmatch_regex;
1500 	else if (match_type == FLT_EQUAL) {
1501 		if (FLT_IS_CASE_SENS(match_flag))
1502 			cond->match_func = str_find_equal;
1503 		else
1504 			cond->match_func = str_case_find_equal;
1505 	} else if (match_type == FLT_IN_ADDRESSBOOK) {
1506 		cond->match_func = str_case_find_equal;
1507 	} else {
1508 		if (FLT_IS_CASE_SENS(match_flag))
1509 			cond->match_func = str_find;
1510 		else
1511 			cond->match_func = str_case_find;
1512 	}
1513 
1514 	return cond;
1515 }
1516 
filter_action_new(FilterActionType type,const gchar * str)1517 FilterAction *filter_action_new(FilterActionType type, const gchar *str)
1518 {
1519 	FilterAction *action;
1520 
1521 	action = g_new0(FilterAction, 1);
1522 	action->type = type;
1523 
1524 	action->str_value = (str && *str) ? g_strdup(str) : NULL;
1525 	if (type == FLT_ACTION_COLOR_LABEL && str)
1526 		action->int_value = atoi(str);
1527 	else
1528 		action->int_value = 0;
1529 
1530 	return action;
1531 }
1532 
filter_info_new(void)1533 FilterInfo *filter_info_new(void)
1534 {
1535 	FilterInfo *fltinfo;
1536 
1537 	fltinfo = g_new0(FilterInfo, 1);
1538 	fltinfo->dest_list = NULL;
1539 	fltinfo->move_dest = NULL;
1540 	fltinfo->drop_done = FALSE;
1541 	fltinfo->error = FLT_ERROR_OK;
1542 	fltinfo->last_exec_exit_status = 0;
1543 
1544 	return fltinfo;
1545 }
1546 
filter_junk_rule_create(PrefsAccount * account,FolderItem * default_junk,gboolean is_manual)1547 FilterRule *filter_junk_rule_create(PrefsAccount *account,
1548 				    FolderItem *default_junk,
1549 				    gboolean is_manual)
1550 {
1551 	FilterRule *rule;
1552 	FilterCond *cond;
1553 	FilterAction *action;
1554 	GSList *cond_list = NULL, *action_list = NULL;
1555 	gchar *junk_id = NULL;
1556 	FolderItem *item = NULL;
1557 
1558 	if (!prefs_common.junk_classify_cmd)
1559 		return NULL;
1560 
1561 	if (prefs_common.junk_folder)
1562 		item = folder_find_item_from_identifier(prefs_common.junk_folder);
1563 
1564 	if (!item && account) {
1565 		Folder *folder = NULL;
1566 		GList *list;
1567 
1568 		/* find most suitable Junk folder for account */
1569 
1570 		if (account->inbox && *account->inbox == '#') {
1571 			FolderItem *inbox;
1572 			inbox = folder_find_item_from_identifier(account->inbox);
1573 			if (inbox) {
1574 				folder = inbox->folder;
1575 				if (folder)
1576 					item = folder_get_junk(folder);
1577 			}
1578 		}
1579 		if (!item) {
1580 			folder = FOLDER(account->folder);
1581 			if (folder)
1582 				item = folder_get_junk(folder);
1583 		}
1584 		if (!item) {
1585 			for (list = folder_get_list(); list != NULL; list = list->next) {
1586 				folder = FOLDER(list->data);
1587 				if (FOLDER_IS_LOCAL(folder)) {
1588 					if (folder->account == account)
1589 						item = folder_get_junk(folder);
1590 					if (!item && folder->node) {
1591 						item = FOLDER_ITEM(folder->node->data);
1592 						if (item && item->account == account && item->folder)
1593 							item = folder_get_junk(item->folder);
1594 						else
1595 							item = NULL;
1596 					}
1597 				}
1598 				if (item)
1599 					break;
1600 			}
1601 		}
1602 	}
1603 
1604 	if (!item)
1605 		item = default_junk;
1606 	if (!item)
1607 		item = folder_get_default_junk();
1608 	if (!item)
1609 		return NULL;
1610 	junk_id = folder_item_get_identifier(item);
1611 	if (!junk_id)
1612 		return NULL;
1613 
1614 	debug_print("filter_junk_rule_create: junk folder: %s\n",
1615 		    junk_id);
1616 
1617 	if (prefs_common.nofilter_junk_sender_in_book) {
1618 		cond = filter_cond_new(FLT_COND_HEADER, FLT_IN_ADDRESSBOOK,
1619 				       FLT_NOT_MATCH, "From", NULL);
1620 		cond_list = g_slist_append(cond_list, cond);
1621 	}
1622 
1623 	cond = filter_cond_new(FLT_COND_CMD_TEST, 0, 0, NULL,
1624 			       prefs_common.junk_classify_cmd);
1625 	cond_list = g_slist_append(cond_list, cond);
1626 
1627 	if (prefs_common.delete_junk_on_recv && !is_manual) {
1628 		action = filter_action_new(FLT_ACTION_COPY, junk_id);
1629 		action_list = g_slist_append(NULL, action);
1630 		action = filter_action_new(FLT_ACTION_DELETE, NULL);
1631 		action_list = g_slist_append(action_list, action);
1632 	} else {
1633 		action = filter_action_new(FLT_ACTION_MOVE, junk_id);
1634 		action_list = g_slist_append(NULL, action);
1635 	}
1636 
1637 	if (prefs_common.mark_junk_as_read) {
1638 		action = filter_action_new(FLT_ACTION_MARK_READ, NULL);
1639 		action_list = g_slist_append(action_list, action);
1640 	}
1641 
1642 	if (is_manual)
1643 		rule = filter_rule_new(_("Junk mail filter (manual)"), FLT_AND,
1644 				       cond_list, action_list);
1645 	else
1646 		rule = filter_rule_new(_("Junk mail filter"), FLT_AND,
1647 				       cond_list, action_list);
1648 
1649 	g_free(junk_id);
1650 
1651         return rule;
1652 }
1653 
filter_rule_rename_dest_path(FilterRule * rule,const gchar * old_path,const gchar * new_path)1654 void filter_rule_rename_dest_path(FilterRule *rule, const gchar *old_path,
1655 				  const gchar *new_path)
1656 {
1657 	FilterAction *action;
1658 	GSList *cur;
1659 	gchar *base;
1660 	gchar *dest_path;
1661 	gint oldpathlen;
1662 
1663 	oldpathlen = strlen(old_path);
1664 
1665 	for (cur = rule->action_list; cur != NULL; cur = cur->next) {
1666 		action = (FilterAction *)cur->data;
1667 
1668 		if (action->type != FLT_ACTION_MOVE &&
1669 		    action->type != FLT_ACTION_COPY)
1670 			continue;
1671 
1672 		if (action->str_value &&
1673 		    !strncmp(old_path, action->str_value, oldpathlen)) {
1674 			base = action->str_value + oldpathlen;
1675 			if (*base != '/' && *base != '\0')
1676 				continue;
1677 			while (*base == '/') base++;
1678 			if (*base == '\0')
1679 				dest_path = g_strdup(new_path);
1680 			else
1681 				dest_path = g_strconcat(new_path, "/", base,
1682 							NULL);
1683 			debug_print("filter_rule_rename_dest_path(): "
1684 				    "renaming %s -> %s\n",
1685 				    action->str_value, dest_path);
1686 			g_free(action->str_value);
1687 			action->str_value = dest_path;
1688 		}
1689 	}
1690 }
1691 
filter_rule_delete_action_by_dest_path(FilterRule * rule,const gchar * path)1692 void filter_rule_delete_action_by_dest_path(FilterRule *rule, const gchar *path)
1693 {
1694 	FilterAction *action;
1695 	GSList *cur;
1696 	GSList *next;
1697 	gint pathlen;
1698 
1699 	pathlen = strlen(path);
1700 
1701 	for (cur = rule->action_list; cur != NULL; cur = next) {
1702 		action = (FilterAction *)cur->data;
1703 		next = cur->next;
1704 
1705 		if (action->type != FLT_ACTION_MOVE &&
1706 		    action->type != FLT_ACTION_COPY)
1707 			continue;
1708 
1709 		if (action->str_value &&
1710 		    !strncmp(path, action->str_value, pathlen) &&
1711 		    (action->str_value[pathlen] == '/' ||
1712 		     action->str_value[pathlen] == '\0')) {
1713 			debug_print("filter_rule_delete_action_by_dest_path(): "
1714 				    "deleting %s\n", action->str_value);
1715 			rule->action_list = g_slist_remove
1716 				(rule->action_list, action);
1717 			filter_action_free(action);
1718 		}
1719 	}
1720 }
1721 
filter_list_rename_path(const gchar * old_path,const gchar * new_path)1722 void filter_list_rename_path(const gchar *old_path, const gchar *new_path)
1723 {
1724 	GSList *cur;
1725 
1726 	g_return_if_fail(old_path != NULL);
1727 	g_return_if_fail(new_path != NULL);
1728 
1729 	for (cur = prefs_common.fltlist; cur != NULL; cur = cur->next) {
1730 		FilterRule *rule = (FilterRule *)cur->data;
1731 		filter_rule_rename_dest_path(rule, old_path, new_path);
1732 	}
1733 
1734 	filter_write_config();
1735 }
1736 
filter_list_delete_path(const gchar * path)1737 void filter_list_delete_path(const gchar *path)
1738 {
1739 	GSList *cur;
1740 	GSList *next;
1741 
1742 	g_return_if_fail(path != NULL);
1743 
1744 	for (cur = prefs_common.fltlist; cur != NULL; cur = next) {
1745 		FilterRule *rule = (FilterRule *)cur->data;
1746 		next = cur->next;
1747 
1748 		filter_rule_delete_action_by_dest_path(rule, path);
1749 		if (!rule->action_list) {
1750 			prefs_common.fltlist =
1751 				g_slist_remove(prefs_common.fltlist, rule);
1752 			filter_rule_free(rule);
1753 		}
1754 	}
1755 
1756 	filter_write_config();
1757 }
1758 
filter_rule_match_type_str_to_enum(const gchar * match_type,FilterMatchType * type,FilterMatchFlag * flag)1759 void filter_rule_match_type_str_to_enum(const gchar *match_type,
1760 					FilterMatchType *type,
1761 					FilterMatchFlag *flag)
1762 {
1763 	g_return_if_fail(match_type != NULL);
1764 
1765 	*type = FLT_CONTAIN;
1766 	*flag = 0;
1767 
1768 	if (!strcmp(match_type, "contains")) {
1769 		*type = FLT_CONTAIN;
1770 	} else if (!strcmp(match_type, "not-contain")) {
1771 		*type = FLT_CONTAIN;
1772 		*flag = FLT_NOT_MATCH;
1773 	} else if (!strcmp(match_type, "is")) {
1774 		*type = FLT_EQUAL;
1775 	} else if (!strcmp(match_type, "is-not")) {
1776 		*type = FLT_EQUAL;
1777 		*flag = FLT_NOT_MATCH;
1778 	} else if (!strcmp(match_type, "regex")) {
1779 		*type = FLT_REGEX;
1780 	} else if (!strcmp(match_type, "not-regex")) {
1781 		*type = FLT_REGEX;
1782 		*flag = FLT_NOT_MATCH;
1783 	} else if (!strcmp(match_type, "in-addressbook")) {
1784 		*type = FLT_IN_ADDRESSBOOK;
1785 	} else if (!strcmp(match_type, "not-in-addressbook")) {
1786 		*type = FLT_IN_ADDRESSBOOK;
1787 		*flag = FLT_NOT_MATCH;
1788 	} else if (!strcmp(match_type, "gt")) {
1789 	} else if (!strcmp(match_type, "lt")) {
1790 		*flag = FLT_NOT_MATCH;
1791 	}
1792 }
1793 
filter_get_keyword_from_msg(MsgInfo * msginfo,gchar ** header,gchar ** key,FilterCreateType type)1794 void filter_get_keyword_from_msg(MsgInfo *msginfo, gchar **header, gchar **key,
1795 				 FilterCreateType type)
1796 {
1797 	static HeaderEntry hentry[] = {{"List-Id:",	   NULL, TRUE},
1798 				       {"X-ML-Name:",	   NULL, TRUE},
1799 				       {"X-List:",	   NULL, TRUE},
1800 				       {"X-Mailing-list:", NULL, TRUE},
1801 				       {"X-Sequence:",	   NULL, TRUE},
1802 				       {NULL,		   NULL, FALSE}};
1803 	enum
1804 	{
1805 		H_LIST_ID	 = 0,
1806 		H_X_ML_NAME	 = 1,
1807 		H_X_LIST	 = 2,
1808 		H_X_MAILING_LIST = 3,
1809 		H_X_SEQUENCE	 = 4
1810 	};
1811 
1812 	FILE *fp;
1813 
1814 	g_return_if_fail(msginfo != NULL);
1815 	g_return_if_fail(header != NULL);
1816 	g_return_if_fail(key != NULL);
1817 
1818 	*header = NULL;
1819 	*key = NULL;
1820 
1821 	switch (type) {
1822 	case FLT_BY_NONE:
1823 		return;
1824 	case FLT_BY_AUTO:
1825 		if ((fp = procmsg_open_message(msginfo)) == NULL)
1826 			return;
1827 		procheader_get_header_fields(fp, hentry);
1828 		fclose(fp);
1829 
1830 #define SET_FILTER_KEY(hstr, idx)	\
1831 {					\
1832 	*header = g_strdup(hstr);	\
1833 	*key = hentry[idx].body;	\
1834 	hentry[idx].body = NULL;	\
1835 }
1836 
1837 		if (hentry[H_LIST_ID].body != NULL) {
1838 			SET_FILTER_KEY("List-Id", H_LIST_ID);
1839 			extract_list_id_str(*key);
1840 		} else if (hentry[H_X_ML_NAME].body != NULL) {
1841 			SET_FILTER_KEY("X-ML-Name", H_X_ML_NAME);
1842 		} else if (hentry[H_X_LIST].body != NULL) {
1843 			SET_FILTER_KEY("X-List", H_X_LIST);
1844 		} else if (hentry[H_X_MAILING_LIST].body != NULL) {
1845 			SET_FILTER_KEY("X-Mailing-list", H_X_MAILING_LIST);
1846 		} else if (hentry[H_X_SEQUENCE].body != NULL) {
1847 			gchar *p;
1848 
1849 			SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
1850 			p = *key;
1851 			while (*p != '\0') {
1852 				while (*p != '\0' && !g_ascii_isspace(*p)) p++;
1853 				while (g_ascii_isspace(*p)) p++;
1854 				if (g_ascii_isdigit(*p)) {
1855 					*p = '\0';
1856 					break;
1857 				}
1858 			}
1859 			g_strstrip(*key);
1860 		} else if (msginfo->subject) {
1861 			*header = g_strdup("Subject");
1862 			*key = g_strdup(msginfo->subject);
1863 		}
1864 
1865 #undef SET_FILTER_KEY
1866 
1867 		g_free(hentry[H_LIST_ID].body);
1868 		hentry[H_LIST_ID].body = NULL;
1869 		g_free(hentry[H_X_ML_NAME].body);
1870 		hentry[H_X_ML_NAME].body = NULL;
1871 		g_free(hentry[H_X_LIST].body);
1872 		hentry[H_X_LIST].body = NULL;
1873 		g_free(hentry[H_X_MAILING_LIST].body);
1874 		hentry[H_X_MAILING_LIST].body = NULL;
1875 
1876 		break;
1877 	case FLT_BY_FROM:
1878 		*header = g_strdup("From");
1879 		*key = g_strdup(msginfo->from);
1880 		break;
1881 	case FLT_BY_TO:
1882 		*header = g_strdup("To");
1883 		*key = g_strdup(msginfo->to);
1884 		break;
1885 	case FLT_BY_SUBJECT:
1886 		*header = g_strdup("Subject");
1887 		*key = g_strdup(msginfo->subject);
1888 		break;
1889 	default:
1890 		break;
1891 	}
1892 }
1893 
filter_rule_list_free(GSList * fltlist)1894 void filter_rule_list_free(GSList *fltlist)
1895 {
1896 	GSList *cur;
1897 
1898 	for (cur = fltlist; cur != NULL; cur = cur->next)
1899 		filter_rule_free((FilterRule *)cur->data);
1900 	g_slist_free(fltlist);
1901 }
1902 
filter_rule_free(FilterRule * rule)1903 void filter_rule_free(FilterRule *rule)
1904 {
1905 	if (!rule) return;
1906 
1907 	g_free(rule->name);
1908 	g_free(rule->target_folder);
1909 
1910 	filter_cond_list_free(rule->cond_list);
1911 	filter_action_list_free(rule->action_list);
1912 
1913 	g_free(rule);
1914 }
1915 
filter_cond_list_free(GSList * cond_list)1916 void filter_cond_list_free(GSList *cond_list)
1917 {
1918 	GSList *cur;
1919 
1920 	for (cur = cond_list; cur != NULL; cur = cur->next)
1921 		filter_cond_free((FilterCond *)cur->data);
1922 	g_slist_free(cond_list);
1923 }
1924 
filter_action_list_free(GSList * action_list)1925 void filter_action_list_free(GSList *action_list)
1926 {
1927 	GSList *cur;
1928 
1929 	for (cur = action_list; cur != NULL; cur = cur->next)
1930 		filter_action_free((FilterAction *)cur->data);
1931 	g_slist_free(action_list);
1932 }
1933 
filter_cond_free(FilterCond * cond)1934 static void filter_cond_free(FilterCond *cond)
1935 {
1936 	g_free(cond->header_name);
1937 	g_free(cond->str_value);
1938 	g_free(cond);
1939 }
1940 
filter_action_free(FilterAction * action)1941 static void filter_action_free(FilterAction *action)
1942 {
1943 	g_free(action->str_value);
1944 	g_free(action);
1945 }
1946 
filter_info_free(FilterInfo * fltinfo)1947 void filter_info_free(FilterInfo *fltinfo)
1948 {
1949 	g_slist_free(fltinfo->dest_list);
1950 	g_free(fltinfo);
1951 }
1952