tin  2.6.1
About: TIN is a threaded NNTP and spool based UseNet newsreader.
  Fossies Dox: tin-2.6.1.tar.xz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

filter.c
Go to the documentation of this file.
1/*
2 * Project : tin - a Usenet reader
3 * Module : filter.c
4 * Author : I. Lea
5 * Created : 1992-12-28
6 * Updated : 2021-08-07
7 * Notes : Filter articles. Kill & auto selection are supported.
8 *
9 * Copyright (c) 1991-2022 Iain Lea <iain@bricbrac.de>
10 * All rights reserved.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 *
16 * 1. Redistributions of source code must retain the above copyright notice,
17 * this list of conditions and the following disclaimer.
18 *
19 * 2. Redistributions in binary form must reproduce the above copyright
20 * notice, this list of conditions and the following disclaimer in the
21 * documentation and/or other materials provided with the distribution.
22 *
23 * 3. Neither the name of the copyright holder nor the names of its
24 * contributors may be used to endorse or promote products derived from
25 * this software without specific prior written permission.
26 *
27 * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
31 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37 * POSSIBILITY OF SUCH DAMAGE.
38 */
39
40
41#ifndef TIN_H
42# include "tin.h"
43#endif /* !TIN_H */
44#ifndef VERSION_H
45# include "version.h"
46#endif /* !VERSION_H */
47#ifndef TCURSES_H
48# include "tcurses.h"
49#endif /* !TCURSES_H */
50
51
52#define IS_READ(i) (arts[i].status == ART_READ)
53#define IS_KILLED(i) (arts[i].killed)
54#define IS_KILLED_UNREAD(i) (arts[i].killed == ART_KILLED_UNREAD)
55#define IS_SELECTED(i) (arts[i].selected)
56
57/*
58 * SET_FILTER in group grp, current article arts[i], with rule ptr[j]
59 *
60 * filtering is now done this way:
61 * a. set score for all articles and rules
62 * b. check each article if the score is above or below the limit
63 *
64 * SET_FILTER is now somewhat shorter, as the real filtering is done
65 * at the end of filter_articles()
66 */
67
68#define SET_FILTER(grp, i, j) \
69 if (ptr[j].score > 0) { \
70 arts[i].score = (SCORE_MAX - ptr[j].score >= arts[i].score) ? \
71 (arts[i].score + ptr[j].score) : SCORE_MAX ; \
72 } else { \
73 arts[i].score = (-SCORE_MAX - ptr[j].score <= arts[i].score) ? \
74 (arts[i].score + ptr[j].score) : -SCORE_MAX ; }
75
76/*
77 * These will probably go away when filtering is rewritten
78 * Easier access to hashed msgids. Note that in REFS(), y must be free()d
79 * msgid is mandatory in an article and cannot be NULL
80 */
81#define MSGID(x) (x->refptr ? x->refptr->txt : "")
82#define REFS(x,y) ((y = get_references(x->refptr ? x->refptr->parent : NULL)) ? y : "")
83
84/*
85 * global filter array
86 */
87struct t_filters glob_filter = { 0, 0, (struct t_filter *) 0 };
88
89
90/*
91 * Global filter file offset
92 */
94
95
96/*
97 * Local prototypes
98 */
99static int get_choice(int x, const char *help, const char *prompt, char *list[], int list_size);
100static int set_filter_scope(struct t_group *group);
101static struct t_filter_comment *add_filter_comment(struct t_filter_comment *ptr, char *text);
102static struct t_filter_comment *free_filter_comment(struct t_filter_comment *ptr);
103static struct t_filter_comment *copy_filter_comment(struct t_filter_comment *from, struct t_filter_comment *to);
104static t_bool add_filter_rule(struct t_group *group, struct t_article *art, struct t_filter_rule *rule, t_bool quick_filter_rule);
105static int test_regex(const char *string, char *regex, t_bool nocase, struct regex_cache *cache);
106static void expand_filter_array(struct t_filters *ptr);
107static void fmt_filter_menu_prompt(char *dest, size_t dest_len, const char *fmt_str, int len, const char *text);
108static void free_filter_item(struct t_filter *ptr);
109static void print_filter_menu(void);
110static void set_filter(struct t_filter *ptr);
111static void write_filter_array(FILE *fp, struct t_filters *ptr);
112#if 0 /* currently unused */
113 static FILE *open_xhdr_fp(char *header, long min, long max);
114#endif /* 0 */
115
116
117/*
118 * Add one more entry to the filter-comment-list.
119 * If ptr == NULL the list will be created.
120 */
121static struct t_filter_comment *
123 struct t_filter_comment *ptr,
124 char *text)
125{
126 if (ptr == NULL) {
127 ptr = my_malloc(sizeof(struct t_filter_comment));
128 ptr->text = my_strdup(text);
129 ptr->next = (struct t_filter_comment *) 0;
130 } else
131 ptr->next = add_filter_comment(ptr->next, text);
132
133 return ptr;
134}
135
136
137/*
138 * Free all entries in a filter-comment-list.
139 * Set ptr to NULL and return it.
140 */
141static struct t_filter_comment *
143 struct t_filter_comment *ptr)
144{
145 struct t_filter_comment *tmp, *next;
146
147 tmp = ptr;
148 while (tmp != NULL) {
149 next = tmp->next;
150 free(tmp->text);
151 free(tmp);
152 tmp = next;
153 }
154
155 return tmp;
156}
157
158
159/*
160 * Copy the filter-comment-list 'from' into the list 'to'.
161 */
162static struct t_filter_comment *
164 struct t_filter_comment *from,
165 struct t_filter_comment *to)
166{
167 if (from != NULL) {
168 to = my_malloc(sizeof(struct t_filter_comment));
169 to->text = my_strdup(from->text);
170 to->next = copy_filter_comment(from->next, NULL);
171 }
172
173 return to;
174}
175
176
177static void
179 struct t_filters *ptr)
180{
181 int num;
182 size_t block;
183
184 num = ++ptr->max;
185
186 block = (size_t) num * sizeof(struct t_filter);
187
188 if (num == 1) /* allocate */
189 ptr->filter = my_malloc(block);
190 else /* reallocate */
191 ptr->filter = my_realloc(ptr->filter, block);
192}
193
194
195/*
196 * Looks for a matching filter hit (wildmat or pcre regex) in the supplied string
197 * If the cache is not yet initialised, compile and optimise the regex
198 * Returns 1 if we hit the rule
199 * Returns 0 if we had no match
200 * In case of error prints an error message and returns -1
201 */
202static int
204 const char *string,
205 char *regex,
206 t_bool nocase,
207 struct regex_cache *cache)
208{
209 int regex_errpos;
210
211 if (!tinrc.wildcard) {
212 if (wildmat(string, regex, nocase))
213 return 1;
214 } else {
215 if (!cache->re)
216 compile_regex(regex, cache, (nocase ? PCRE_CASELESS : 0));
217 if (cache->re) {
218 regex_errpos = pcre_exec(cache->re, cache->extra, string, (int) strlen(string), 0, 0, NULL, 0);
219 if (regex_errpos >= 0)
220 return 1;
221 else if (regex_errpos != PCRE_ERROR_NOMATCH) { /* also exclude PCRE_ERROR_BADUTF8 ? */
222 error_message(2, _(txt_pcre_error_num), regex_errpos);
223#ifdef DEBUG
224 if (debug & DEBUG_FILTER) {
225 debug_print_file("FILTER", _(txt_pcre_error_num), regex_errpos);
226 debug_print_file("FILTER", "\t regex: %s", regex);
227 debug_print_file("FILTER", "\tstring: %s", string);
228 }
229#endif /* DEBUG */
230 return -1;
231 }
232 }
233 }
234 return 0;
235}
236
237
238/*
239 * set_filter() initialises a struct t_filter with default values
240 */
241static void
243 struct t_filter *ptr)
244{
245 if (ptr != NULL) {
246 ptr->comment = (struct t_filter_comment *) 0;
247 ptr->scope = NULL;
248 ptr->inscope = TRUE;
249 ptr->icase = FALSE;
250 ptr->fullref = FILTER_MSGID;
251 ptr->subj = NULL;
252 ptr->from = NULL;
253 ptr->msgid = NULL;
255 ptr->lines_num = 0;
257 ptr->gnksa_num = 0;
258 ptr->score = 0;
259 ptr->xref = NULL;
260 ptr->path = NULL;
261 ptr->time = (time_t) 0;
262 ptr->next = (struct t_filter *) 0;
263 }
264}
265
266
267/*
268 * free_filter_item() frees all filter data (char *)
269 */
270static void
272 struct t_filter *ptr)
273{
275 FreeAndNull(ptr->scope);
276 FreeAndNull(ptr->subj);
277 FreeAndNull(ptr->from);
278 FreeAndNull(ptr->msgid);
279 FreeAndNull(ptr->xref);
280 FreeAndNull(ptr->path);
281}
282
283
284/*
285 * free_filter_array() frees t_filter structs t_filters contains pointers to
286 */
287void
289 struct t_filters *ptr)
290{
291 int i;
292
293 if (ptr != NULL) {
294 for (i = 0; i < ptr->num; i++)
295 free_filter_item(ptr->filter + i);
296
297 FreeAndNull(ptr->filter);
298 ptr->num = 0;
299 ptr->max = 0;
300 }
301}
302
303
304/*
305 * read ~/.tin/filter file contents into filter array
306 */
307t_bool
309 const char *file)
310{
311 FILE *fp;
312 char buf[HEADER_LEN];
313 char scope[HEADER_LEN];
314 char comment_line[LEN]; /* one line of comment */
315 char subj[HEADER_LEN];
316 char from[HEADER_LEN];
317 char msgid[HEADER_LEN];
318 char buffer[HEADER_LEN];
319 char gnksa[HEADER_LEN];
320 char xref[HEADER_LEN];
321 char path[HEADER_LEN];
322 char scbuf[PATH_LEN];
323 int i = 0;
324 int icase = 0;
325 int score = 0;
326 long secs = 0L;
327 struct t_filter_comment *comment = NULL;
328 struct t_filter *ptr = NULL;
329 t_bool need_write = FALSE;
330 t_bool no_version_line = TRUE;
331 t_bool expired_time = FALSE;
332 time_t current_secs = (time_t) 0;
333 static t_bool first_read = TRUE;
334 struct t_version *upgrade = NULL;
335
336 if ((fp = fopen(file, "r")) == NULL)
337 return FALSE;
338
339 if (!batch_mode || verbose)
341
342 (void) time(&current_secs);
343
344 /*
345 * Reset all filter arrays if doing a reread of the active file
346 */
347 if (!first_read)
349
351 scope[0] = '\0';
352 while (fgets(buf, (int) sizeof(buf), fp) != NULL) {
353 if (*buf == '\n')
354 continue;
355 if (*buf == '#') {
356 if (scope[0] == '\0')
358 if (upgrade == NULL && first_read && match_string(buf, "# Filter file V", NULL, 0)) {
359 first_read = FALSE;
360 no_version_line = FALSE;
361 upgrade = check_upgrade(buf, "# Filter file V", FILTER_VERSION);
362 if (upgrade->state != RC_IGNORE)
363 upgrade_prompt_quit(upgrade, file); /* FILTER_FILE */ /* TODO: do something (more) useful here */
364 }
365 continue;
366 }
367
368 switch (my_tolower((unsigned char) buf[0])) {
369 case 'c':
370 if (match_integer(buf + 1, "ase=", &icase, 1)) {
371 if (ptr && !expired_time)
372 ptr[i].icase = (unsigned) icase;
373
374 break;
375 }
376 if (match_string(buf + 1, "omment=", comment_line, sizeof(comment_line))) {
377 str_trim(comment_line);
378 comment = add_filter_comment(comment, comment_line);
379 }
380 break;
381
382 case 'f':
383 if (match_string(buf + 1, "rom=", from, sizeof(from))) {
384 if (ptr && !expired_time) {
385 if (tinrc.wildcard && ptr[i].from != NULL) {
386 /* merge with already read value */
387 ptr[i].from = my_realloc(ptr[i].from, strlen(ptr[i].from) + strlen(from) + 2);
388 strcat(ptr[i].from, "|");
389 strcat(ptr[i].from, from);
390 } else {
391 FreeIfNeeded(ptr[i].from);
392 ptr[i].from = my_strdup(from);
393 }
394 }
395 }
396 break;
397
398 case 'g':
399 if (match_string(buf + 1, "roup=", scope, sizeof(scope))) {
400 str_trim(scope);
401#ifdef DEBUG
402 if (debug & DEBUG_FILTER)
403 debug_print_file("FILTER", "\nnum=[%d] group=[%s]", glob_filter.num, scope);
404#endif /* DEBUG */
407
408 ptr = glob_filter.filter;
409 i = glob_filter.num++;
410 set_filter(&ptr[i]);
411 expired_time = FALSE;
412 ptr[i].scope = my_strdup(scope);
413 if (comment != NULL) {
414 ptr[i].comment = copy_filter_comment(comment, ptr[i].comment);
415 comment = free_filter_comment(comment);
416 }
417 subj[0] = '\0';
418 from[0] = '\0';
419 msgid[0] = '\0';
420 buffer[0] = '\0';
421 xref[0] = '\0';
422 path[0] = '\0';
423 icase = 0;
424 secs = 0L;
425 break;
426 }
427 if (match_string(buf + 1, "nksa=", gnksa, sizeof(gnksa))) {
428 if (ptr && !expired_time) {
429 if (gnksa[0] == '<') {
430 ptr[i].gnksa_cmp = FILTER_LINES_LT;
431 ptr[i].gnksa_num = atoi(&gnksa[1]);
432 } else if (gnksa[0] == '>') {
433 ptr[i].gnksa_cmp = FILTER_LINES_GT;
434 ptr[i].gnksa_num = atoi(&gnksa[1]);
435 } else {
436 ptr[i].gnksa_cmp = FILTER_LINES_EQ;
437 ptr[i].gnksa_num = atoi(gnksa);
438 }
439 }
440 }
441 break;
442
443 case 'l':
444 if (match_string(buf + 1, "ines=", buffer, sizeof(buffer))) {
445 if (ptr && !expired_time) {
446 if (buffer[0] == '<') {
447 ptr[i].lines_cmp = FILTER_LINES_LT;
448 ptr[i].lines_num = atoi(&buffer[1]);
449 } else if (buffer[0] == '>') {
450 ptr[i].lines_cmp = FILTER_LINES_GT;
451 ptr[i].lines_num = atoi(&buffer[1]);
452 } else {
453 ptr[i].lines_cmp = FILTER_LINES_EQ;
454 ptr[i].lines_num = atoi(buffer);
455 }
456 }
457 }
458 break;
459
460 case 'm':
461 if (match_string(buf + 1, "sgid=", msgid, sizeof(msgid))) {
462 if (ptr && !expired_time) {
463 if (tinrc.wildcard && ptr[i].msgid != NULL && ptr[i].fullref == FILTER_MSGID) {
464 /* merge with already read value */
465 ptr[i].msgid = my_realloc(ptr[i].msgid, strlen(ptr[i].msgid) + strlen(msgid) + 2);
466 strcat(ptr[i].msgid, "|");
467 strcat(ptr[i].msgid, msgid);
468 } else {
469 FreeIfNeeded(ptr[i].msgid);
470 ptr[i].msgid = my_strdup(msgid);
471 ptr[i].fullref = FILTER_MSGID;
472 }
473 }
474 break;
475 }
476 if (match_string(buf + 1, "sgid_last=", msgid, sizeof(msgid))) {
477 if (ptr && !expired_time) {
478 if (tinrc.wildcard && ptr[i].msgid != NULL && ptr[i].fullref == FILTER_MSGID_LAST) {
479 /* merge with already read value */
480 ptr[i].msgid = my_realloc(ptr[i].msgid, strlen(ptr[i].msgid) + strlen(msgid) + 2);
481 strcat(ptr[i].msgid, "|");
482 strcat(ptr[i].msgid, msgid);
483 } else {
484 FreeIfNeeded(ptr[i].msgid);
485 ptr[i].msgid = my_strdup(msgid);
486 ptr[i].fullref = FILTER_MSGID_LAST;
487 }
488 }
489 break;
490 }
491 if (match_string(buf + 1, "sgid_only=", msgid, sizeof(msgid))) {
492 if (ptr && !expired_time) {
493 if (tinrc.wildcard && ptr[i].msgid != NULL && ptr[i].fullref == FILTER_MSGID_ONLY) {
494 /* merge with already read value */
495 ptr[i].msgid = my_realloc(ptr[i].msgid, strlen(ptr[i].msgid) + strlen(msgid) + 2);
496 strcat(ptr[i].msgid, "|");
497 strcat(ptr[i].msgid, msgid);
498 } else {
499 FreeIfNeeded(ptr[i].msgid);
500 ptr[i].msgid = my_strdup(msgid);
501 ptr[i].fullref = FILTER_MSGID_ONLY;
502 }
503 }
504 }
505 break;
506
507 case 'p':
508 if (match_string(buf + 1, "ath=", path, sizeof(path))) {
509 str_trim(path);
510 if (ptr && !expired_time) {
511 if (tinrc.wildcard && ptr[i].path != NULL) {
512 /* merge with already read value */
513 ptr[i].path = my_realloc(ptr[i].path, strlen(ptr[i].path) + strlen(path) + 2);
514 strcat(ptr[i].path, "|");
515 strcat(ptr[i].path, path);
516 } else {
517 FreeIfNeeded(ptr[i].path);
518 ptr[i].path = my_strdup(path);
519 }
520 }
521 }
522 break;
523
524 case 'r':
525 if (match_string(buf + 1, "efs_only=", msgid, sizeof(msgid))) {
526 if (ptr && !expired_time) {
527 if (tinrc.wildcard && ptr[i].msgid != NULL && ptr[i].fullref == FILTER_REFS_ONLY) {
528 /* merge with already read value */
529 ptr[i].msgid = my_realloc(ptr[i].msgid, strlen(ptr[i].msgid) + strlen(msgid) + 2);
530 strcat(ptr[i].msgid, "|");
531 strcat(ptr[i].msgid, msgid);
532 } else {
533 FreeIfNeeded(ptr[i].msgid);
534 ptr[i].msgid = my_strdup(msgid);
535 ptr[i].fullref = FILTER_REFS_ONLY;
536 }
537 }
538 }
539 break;
540
541 case 's':
542 if (match_string(buf + 1, "ubj=", subj, sizeof(subj))) {
543 if (ptr && !expired_time) {
544 if (tinrc.wildcard && ptr[i].subj != NULL) {
545 /* merge with already read value */
546 ptr[i].subj = my_realloc(ptr[i].subj, strlen(ptr[i].subj) + strlen(subj) + 2);
547 strcat(ptr[i].subj, "|");
548 strcat(ptr[i].subj, subj);
549 } else {
550 FreeIfNeeded(ptr[i].subj);
551 ptr[i].subj = my_strdup(subj);
552 }
553#ifdef DEBUG
554 if (debug & DEBUG_FILTER)
555 debug_print_file("FILTER", "buf=[%s] Gsubj=[%s]", ptr[i].subj, glob_filter.filter[i].subj);
556#endif /* DEBUG */
557 }
558 break;
559 }
560
561 /*
562 * read score for rule
563 */
564 if (match_string(buf + 1, "core=", scbuf, PATH_LEN)) {
565 score = atoi(scbuf);
566#ifdef DEBUG
567 if (debug & DEBUG_FILTER)
568 debug_print_file("FILTER", "score=[%d]", score);
569#endif /* DEBUG */
570 if (ptr && !expired_time) {
571 if (score > SCORE_MAX)
572 score = SCORE_MAX;
573 else {
574 if (score < -SCORE_MAX)
575 score = -SCORE_MAX;
576 else {
577 if (!score) {
578 if (!strncasecmp(scbuf, "kill", 4))
579 score = tinrc.score_kill;
580 else {
581 if (!strncasecmp(scbuf, "hot", 3))
582 score = tinrc.score_select;
583 }
584 }
585 }
586 }
587 ptr[i].score = score;
588 }
589 }
590 break;
591
592 case 't':
593 if (match_long(buf + 1, "ime=", &secs)) {
594 if (ptr && !expired_time) {
595 ptr[i].time = (time_t) secs;
596 /* rule expired? */
597 if (secs && current_secs > (time_t) secs) {
598#ifdef DEBUG
599 if (debug & DEBUG_FILTER)
600 debug_print_file("FILTER", "EXPIRED secs=[%lu] current_secs=[%lu]", (unsigned long int) secs, (unsigned long int) current_secs);
601#endif /* DEBUG */
603 expired_time = TRUE;
604 need_write = TRUE;
605 }
606 }
607 }
608 break;
609
610 case 'x':
611 /*
612 * TODO: format has changed in FILTER_VERSION 1.0.0,
613 * should we comment out older xref rules like below?
614 */
615 if (ptr && match_string(buf + 1, "ref=", xref, sizeof(xref))) {
616 str_trim(xref);
617 if (!expired_time) {
618 if (tinrc.wildcard && ptr[i].xref != NULL) {
619 /* merge with already read value */
620 ptr[i].xref = my_realloc(ptr[i].xref, strlen(ptr[i].xref) + strlen(xref) + 2);
621 strcat(ptr[i].xref, "|");
622 strcat(ptr[i].xref, xref);
623 } else {
624 FreeIfNeeded(ptr[i].xref);
625 ptr[i].xref = my_strdup(xref);
626 }
627 }
628 break;
629 }
630 if (ptr && ((upgrade && upgrade->state == RC_UPGRADE) || no_version_line)) {
631 char foo[HEADER_LEN];
632
633 if (match_string(buf + 1, "ref_max=", foo, LEN - 1)) {
634 /* TODO: give better explanation. */
636 ptr[i].comment = add_filter_comment(ptr[i].comment, foo);
637 need_write = TRUE;
638 break;
639 }
640 if (match_string(buf + 1, "ref_score=", foo, LEN - 1)) {
641 /* TODO: give better explanation. */
643 ptr[i].comment = add_filter_comment(ptr[i].comment, foo);
644 need_write = TRUE;
645 }
646 }
647 break;
648
649 default:
650 break;
651 }
652 }
653
654 if (comment) /* stray comment without scope */
655 (void) free_filter_comment(comment);
656
657 fclose(fp);
658
659 if (!upgrade && need_write) {
660 upgrade = my_malloc(sizeof(struct t_version));
661 upgrade->state = RC_UPGRADE;
662 upgrade->file_version = -1;
663 upgrade_prompt_quit(upgrade, file); /* TODO: do something (more) useful here */
664 }
665
666 if (need_write || (upgrade && upgrade->state == RC_UPGRADE))
667 write_filter_file(file);
668
669 if (!cmd_line && !batch_mode)
671
672 FreeAndNull(upgrade);
673 return TRUE;
674}
675
676
677/*
678 * write filter strings to ~/.tin/filter
679 */
680void
682 const char *filename)
683{
684 FILE *fp;
685 char *file_tmp;
686 int i;
687 long fpos;
688
689 if (no_write)
690 return;
691
692 /* generate tmp-filename */
693 file_tmp = get_tmpfilename(filename);
694
695 if (!backup_file(filename, file_tmp)) {
697 free(file_tmp);
698 return;
699 }
700
701 if ((fp = fopen(filename, "w+")) == NULL) {
702 free(file_tmp);
703 return;
704 }
705
706 /* TODO: -> lang.c */
707 fprintf(fp, "# Filter file V%s for the TIN newsreader\n#\n", FILTER_VERSION);
708 fprintf(fp, "%s", _(txt_filter_file));
709
710 fflush(fp);
711
712 /* determine the file offset */
713 if (!batch_mode) {
714 if ((fpos = ftell(fp)) <= 0) {
715 clearerr(fp);
716 fclose(fp);
717 rename_file(file_tmp, filename);
718 free(file_tmp);
719 error_message(2, _(txt_filesystem_full), filename);
720 return;
721 }
722 rewind(fp);
724 while ((i = fgetc(fp)) != EOF) {
725 if (i == '\n')
727 }
728 if (fseek(fp, fpos, SEEK_SET)) {
729 clearerr(fp);
730 fclose(fp);
731 rename_file(file_tmp, filename);
732 free(file_tmp);
733 error_message(2, _(txt_filesystem_full), filename);
734 return;
735 }
736 }
737
738 /*
739 * Save global filters
740 */
742
743 if ((i = ferror(fp)) || fclose(fp)) {
744 error_message(2, _(txt_filesystem_full), filename);
745 rename_file(file_tmp, filename);
746 if (i) {
747 clearerr(fp);
748 fclose(fp);
749 }
750 } else
751 unlink(file_tmp);
752
753 free(file_tmp);
754}
755
756
757static void
759 FILE *fp,
760 struct t_filters *ptr)
761{
762 int i;
763 struct t_filter_comment *comment;
764 time_t theTime = time(NULL);
765
766 if (ptr == NULL)
767 return;
768
769 for (i = 0; i < ptr->num; i++) {
770#ifdef DEBUG
771 if (debug & DEBUG_FILTER)
772 debug_print_file("FILTER", "WRITE i=[%d] subj=[%s] from=[%s]\n", i, BlankIfNull(ptr->filter[i].subj), BlankIfNull(ptr->filter[i].from));
773#endif /* DEBUG */
774
775 if (ptr->filter[i].time && theTime > ptr->filter[i].time)
776 continue;
777#ifdef DEBUG
778 if (debug & DEBUG_FILTER)
779 debug_print_file("FILTER", "Scope=[%s]" cCRLF, (ptr->filter[i].scope != NULL ? ptr->filter[i].scope : "*"));
780#endif /* DEBUG */
781
782 fprintf(fp, "\n"); /* makes filter file more readable */
783
784 /* comments appear always first, if there are any... */
785 if (ptr->filter[i].comment != NULL) {
786 /*
787 * Save the start of the list, in case write_filter_array is
788 * called multiple times. Otherwise the list would get lost.
789 */
790 comment = ptr->filter[i].comment;
791 while (ptr->filter[i].comment != NULL) {
792 fprintf(fp, "comment=%s\n", ptr->filter[i].comment->text);
793 ptr->filter[i].comment = ptr->filter[i].comment->next;
794 }
795 ptr->filter[i].comment = comment;
796 }
797
798 fprintf(fp, "group=%s\n", (ptr->filter[i].scope != NULL ? ptr->filter[i].scope : "*"));
799
800 fprintf(fp, "case=%u\n", ptr->filter[i].icase);
801
802 if (ptr->filter[i].score == tinrc.score_kill)
803 fprintf(fp, "score=kill\n");
804 else if (ptr->filter[i].score == tinrc.score_select)
805 fprintf(fp, "score=hot\n");
806 else
807 fprintf(fp, "score=%d\n", ptr->filter[i].score);
808
809 if (ptr->filter[i].subj != NULL)
810 fprintf(fp, "subj=%s\n", ptr->filter[i].subj);
811
812 if (ptr->filter[i].from != NULL)
813 fprintf(fp, "from=%s\n", ptr->filter[i].from);
814
815 if (ptr->filter[i].msgid != NULL) {
816 switch (ptr->filter[i].fullref) {
817 case FILTER_MSGID:
818 fprintf(fp, "msgid=%s\n", ptr->filter[i].msgid);
819 break;
820
822 fprintf(fp, "msgid_last=%s\n", ptr->filter[i].msgid);
823 break;
824
826 fprintf(fp, "msgid_only=%s\n", ptr->filter[i].msgid);
827 break;
828
829 case FILTER_REFS_ONLY:
830 fprintf(fp, "refs_only=%s\n", ptr->filter[i].msgid);
831 break;
832
833 default:
834 break;
835 }
836 }
837
838 if (ptr->filter[i].lines_cmp != FILTER_LINES_NO) {
839 switch (ptr->filter[i].lines_cmp) {
840 case FILTER_LINES_EQ:
841 fprintf(fp, "lines=%d\n", ptr->filter[i].lines_num);
842 break;
843
844 case FILTER_LINES_LT:
845 fprintf(fp, "lines=<%d\n", ptr->filter[i].lines_num);
846 break;
847
848 case FILTER_LINES_GT:
849 fprintf(fp, "lines=>%d\n", ptr->filter[i].lines_num);
850 break;
851
852 default:
853 break;
854 }
855 }
856
857 if (ptr->filter[i].gnksa_cmp != FILTER_LINES_NO) {
858 switch (ptr->filter[i].gnksa_cmp) {
859 case FILTER_LINES_EQ:
860 fprintf(fp, "gnksa=%d\n", ptr->filter[i].gnksa_num);
861 break;
862
863 case FILTER_LINES_LT:
864 fprintf(fp, "gnksa=<%d\n", ptr->filter[i].gnksa_num);
865 break;
866
867 case FILTER_LINES_GT:
868 fprintf(fp, "gnksa=>%d\n", ptr->filter[i].gnksa_num);
869 break;
870
871 default:
872 break;
873 }
874 }
875
876 if (ptr->filter[i].xref != NULL)
877 fprintf(fp, "xref=%s\n", ptr->filter[i].xref);
878
879 if (ptr->filter[i].path != NULL)
880 fprintf(fp, "path=%s\n", ptr->filter[i].path);
881
882 if (ptr->filter[i].time) {
883 char timestring[25];
884 if (my_strftime(timestring, sizeof(timestring) - 1, "%Y-%m-%d %H:%M:%S UTC", gmtime(&(ptr->filter[i].time))))
885 fprintf(fp, "time=%lu (%s)\n", (unsigned long int) ptr->filter[i].time, timestring);
886 }
887 }
888 fflush(fp);
889}
890
891
892/*
893 * Interactive filter menu
894 */
895static int
897 int x,
898 const char *help,
899 const char *prompt,
900 char *list[],
901 int list_size)
902{
903 int ch, y, i = 0;
904
905 if (help)
906 show_menu_help(help);
907
908 if (list == NULL || list_size < 1)
909 return -1;
910
911 y = strwidth(prompt);
912
913 do {
914 MoveCursor(x, y);
915 my_fputs(list[i], stdout);
916 my_flush();
917 CleartoEOLN();
918 ch = ReadCh();
919 switch (ch) {
920 case ' ':
921 i++;
922 i %= list_size;
923 break;
924
925 case ESC: /* (ESC) common arrow keys */
926#ifdef HAVE_KEY_PREFIX
927 case KEY_PREFIX:
928#endif /* HAVE_KEY_PREFIX */
929 switch (get_arrow_key(ch)) {
930 case KEYMAP_UP:
931 i--;
932 if (i < 0)
933 i = list_size - 1;
934 ch = ' '; /* don't exit the while loop yet */
935 break;
936
937 case KEYMAP_DOWN:
938 i++;
939 i %= list_size;
940 ch = ' '; /* don't exit the while loop yet */
941 break;
942
943 default:
944 break;
945 }
946 break;
947
948 default:
949 break;
950 }
951 } while (ch != '\n' && ch != '\r' && ch != iKeyAbort); /* TODO: replace hard coded keynames */
952
953 if (ch == iKeyAbort)
954 return -1;
955
956 return i;
957}
958
959
960static const char *ptr_filter_comment;
961static const char *ptr_filter_lines;
962static const char *ptr_filter_menu;
963static const char *ptr_filter_scope;
964static const char *ptr_filter_text;
965static const char *ptr_filter_time;
966static const char *ptr_filter_groupname;
967static char text_subj[PATH_LEN];
968static char text_from[PATH_LEN];
969static char text_msgid[PATH_LEN];
970static char text_score[PATH_LEN];
971
972
973static void
975 void)
976{
977 ClearScreen();
978
980
985 my_printf("%s%s", text_subj, cCRLF);
986 my_printf("%s%s", text_from, cCRLF);
987 my_printf("%s%s%s", text_msgid, cCRLF, cCRLF);
989 my_printf("%s%s", text_score, cCRLF);
992 my_flush();
993}
994
995
996#if defined(SIGWINCH) || defined(SIGTSTP)
997void
998refresh_filter_menu(
999 void)
1000{
1002
1003 /*
1004 * TODO:
1005 * - refresh already entered and accepted information (follow control
1006 * flow in filter_menu below)
1007 * - refresh help line
1008 * - set cursor into current input field
1009 * - refresh already entered data or selected item in current input field
1010 * (not everywhere possible yet -- must change getline.c for refreshing
1011 * string input)
1012 */
1013}
1014#endif /* SIGWINCH || SIGTSTP */
1015
1016
1017/*
1018 * a help function for filter_menu
1019 * formats a menu option in a multibyte-safe way
1020 *
1021 * this function in closely tight to the way how the filter menu is build
1022 */
1023static void
1025 char *dest, /* where to store the resulting string */
1026 size_t dest_len, /* size of dest */
1027 const char *fmt_str, /* format string */
1028 int len, /* maximal len of the include string */
1029 const char *text) /* the include string */
1030{
1031 char *buf;
1032#if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1033 wchar_t *wbuf, *wbuf2;
1034
1035 if ((wbuf = char2wchar_t(text)) != NULL) {
1036 wbuf2 = wcspart(wbuf, len, TRUE);
1037 if ((buf = wchar_t2char(wbuf2)) == NULL) {
1038 /* conversion failed, truncate original string */
1039 buf = my_malloc(len + 1);
1040 snprintf(buf, (size_t) (len + 1), "%-*.*s", len, len, text);
1041 }
1042
1043 free(wbuf);
1044 free(wbuf2);
1045 } else
1046#endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1047 {
1048 buf = my_malloc(len + 1);
1049 snprintf(buf, (size_t) (len + 1), "%-*.*s", len, len, text);
1050 }
1051 snprintf(dest, dest_len, fmt_str, buf);
1052 free(buf);
1053}
1054
1055
1056/*
1057 * Interactive filter menu so that the user can dynamically enter parameters.
1058 * Can be configured for kill or auto-selection screens.
1059 */
1060t_bool
1062 t_function type,
1063 struct t_group *group,
1064 struct t_article *art)
1065{
1066 const char *ptr_filter_from;
1067 const char *ptr_filter_msgid;
1068 const char *ptr_filter_subj;
1069 const char *ptr_filter_help_scope;
1070 const char *ptr_filter_quit_edit_save;
1071 char *ptr;
1072 char **list;
1073 char comment_line[LEN];
1074 char buf[LEN];
1075 char keyedit[MAXKEYLEN], keyquit[MAXKEYLEN], keysave[MAXKEYLEN];
1076 char text_time[PATH_LEN];
1077 char double_time[PATH_LEN];
1078 char quat_time[PATH_LEN];
1079 int i, len, clen, flen;
1080 struct t_filter_rule rule;
1081 t_bool proceed;
1082 t_bool ret;
1083 t_function func, default_func = FILTER_SAVE;
1084
1086
1087 rule.comment = (struct t_filter_comment *) 0;
1088 rule.text[0] = '\0';
1089 rule.scope[0] = '\0';
1090 rule.counter = 0;
1092 rule.lines_num = 0;
1093 rule.from_ok = FALSE;
1094 rule.lines_ok = FALSE;
1095 rule.msgid_ok = FALSE;
1096 rule.fullref = FILTER_MSGID;
1097 rule.subj_ok = FALSE;
1098 rule.icase = FALSE;
1099 rule.score = 0;
1100 rule.expire_time = FALSE;
1101 rule.check_string = FALSE;
1102
1103 comment_line[0] = '\0';
1104
1105 /*
1106 * setup correct text for user selected menu
1107 */
1111
1112 if (type == GLOBAL_MENU_FILTER_KILL) {
1113 ptr_filter_from = _(txt_kill_from);
1116 ptr_filter_msgid = _(txt_kill_msgid);
1118 ptr_filter_subj = _(txt_kill_subj);
1121 ptr_filter_help_scope = _(txt_help_kill_scope);
1122 ptr_filter_quit_edit_save = _(txt_quit_edit_save_kill);
1123 } else { /* type == GLOBAL_MENU_FILTER_SELECT */
1124 ptr_filter_from = _(txt_select_from);
1127 ptr_filter_msgid = _(txt_select_msgid);
1129 ptr_filter_subj = _(txt_select_subj);
1132 ptr_filter_help_scope = _(txt_help_select_scope);
1133 ptr_filter_quit_edit_save = _(txt_quit_edit_save_select);
1134 }
1135
1137 ptr_filter_groupname = group->name;
1138
1139 clen = strwidth(_(txt_no));
1140 clen = MAX(clen, strwidth(_(txt_yes)));
1141 clen = MAX(clen, strwidth(_(txt_full)));
1142 clen = MAX(clen, strwidth(_(txt_last)));
1143 clen = MAX(clen, strwidth(_(txt_only)));
1144
1145 flen = strwidth(ptr_filter_subj) - 2;
1146 flen = MAX(flen, strwidth(ptr_filter_from) - 2);
1147 flen = MAX(flen, strwidth(ptr_filter_msgid) - 2);
1148
1149 len = cCOLS - flen - clen - 1 + 4;
1150
1151 snprintf(text_time, sizeof(text_time), _(txt_time_default_days), tinrc.filter_days);
1152 fmt_filter_menu_prompt(text_subj, sizeof(text_subj), ptr_filter_subj, len, art->subject);
1154 fmt_filter_menu_prompt(text_from, sizeof(text_from), ptr_filter_from, len, art->from);
1155 fmt_filter_menu_prompt(text_msgid, sizeof(text_msgid), ptr_filter_msgid, len - 4, MSGID(art));
1156
1158
1159 /*
1160 * None, one or multiple lines of comment.
1161 * Continue until an empty line is entered.
1162 * The empty line is ignored.
1163 */
1165 while ((proceed = prompt_menu_string(INDEX_TOP, ptr_filter_comment, comment_line)) && comment_line[0] != '\0') {
1166 rule.comment = add_filter_comment(rule.comment, comment_line);
1167 comment_line[0] = '\0';
1168 }
1169 if (!proceed) {
1170 rule.comment = free_filter_comment(rule.comment);
1171 return FALSE;
1172 }
1173
1174 /*
1175 * Text which might be used to filter on subj, from or msgid
1176 */
1179 rule.comment = free_filter_comment(rule.comment);
1180 return FALSE;
1181 }
1182
1183 if (*rule.text) {
1184 list = my_malloc(sizeof(char *) * 8);
1185 list[0] = (char *) _(txt_subj_line_only_case);
1186 list[1] = (char *) _(txt_subj_line_only);
1187 list[2] = (char *) _(txt_from_line_only_case);
1188 list[3] = (char *) _(txt_from_line_only);
1189 list[4] = (char *) _(txt_msgid_refs_line);
1190 list[5] = (char *) _(txt_msgid_line_last);
1191 list[6] = (char *) _(txt_msgid_line_only);
1192 list[7] = (char *) _(txt_refs_line_only);
1193
1195 free(list);
1196
1197 if (i == -1) {
1198 rule.comment = free_filter_comment(rule.comment);
1199 return FALSE;
1200 }
1201
1202 rule.counter = i;
1203 switch (i) {
1206 rule.icase = TRUE;
1207 break;
1208
1211 case FILTER_MSGID:
1212 case FILTER_MSGID_LAST:
1213 case FILTER_MSGID_ONLY:
1214 case FILTER_REFS_ONLY:
1215 break;
1216
1217 default: /* should not happen */
1218 /* CONSTANTCONDITION */
1219 assert(0 != 0);
1220 break;
1221 }
1222 }
1223
1224 if (!*rule.text) {
1225 rule.check_string = TRUE;
1226 /*
1227 * Subject:
1228 */
1229 list = my_malloc(sizeof(char *) * 2);
1230 list[0] = (char *) _(txt_yes);
1231 list[1] = (char *) _(txt_no);
1233 free(list);
1234
1235 if (i == -1) {
1236 rule.comment = free_filter_comment(rule.comment);
1237 return FALSE;
1238 } else
1239 rule.subj_ok = (i == 0);
1240
1241 /*
1242 * From:
1243 */
1244 list = my_malloc(sizeof(char *) * 2);
1245 if (rule.subj_ok) {
1246 list[0] = (char *) _(txt_no);
1247 list[1] = (char *) _(txt_yes);
1248 } else {
1249 list[0] = (char *) _(txt_yes);
1250 list[1] = (char *) _(txt_no);
1251 }
1253 free(list);
1254
1255 if (i == -1) {
1256 rule.comment = free_filter_comment(rule.comment);
1257 return FALSE;
1258 } else
1259 rule.from_ok = rule.subj_ok ? (i != 0) : (i == 0);
1260
1261 /*
1262 * Message-ID:
1263 */
1264 list = my_malloc(sizeof(char *) * 4);
1265 if (rule.subj_ok || rule.from_ok) {
1266 list[0] = (char *) _(txt_no);
1267 list[1] = (char *) _(txt_full);
1268 list[2] = (char *) _(txt_last);
1269 list[3] = (char *) _(txt_only);
1270 } else {
1271 list[0] = (char *) _(txt_full);
1272 list[1] = (char *) _(txt_last);
1273 list[2] = (char *) _(txt_only);
1274 list[3] = (char *) _(txt_no);
1275 }
1277 free(list);
1278
1279 if (i == -1) {
1280 rule.comment = free_filter_comment(rule.comment);
1281 return FALSE;
1282 } else {
1283 switch ((rule.subj_ok || rule.from_ok) ? i : i + 1) {
1284 case 0:
1285 case 4:
1286 rule.msgid_ok = FALSE;
1287 rule.fullref = FILTER_MSGID;
1288 break;
1289
1290 case 1:
1291 rule.msgid_ok = TRUE;
1292 rule.fullref = FILTER_MSGID;
1293 break;
1294
1295 case 2:
1296 rule.msgid_ok = TRUE;
1298 break;
1299
1300 case 3:
1301 rule.msgid_ok = TRUE;
1303 break;
1304
1305 default: /* should not happen */
1306 /* CONSTANTCONDITION */
1307 assert(0 != 0);
1308 break;
1309 }
1310 }
1311
1312 }
1313
1314 /*
1315 * Lines:
1316 */
1318
1319 buf[0] = '\0';
1320
1322 rule.comment = free_filter_comment(rule.comment);
1323 return FALSE;
1324 }
1325
1326 /*
1327 * Get the < > sign if any for the lines rule
1328 */
1329 ptr = buf;
1330 while (*ptr == ' ')
1331 ptr++;
1332
1333 if (*ptr == '>') {
1335 ptr++;
1336 } else if (*ptr == '<') {
1338 ptr++;
1339 } else if (*ptr == '=') {
1341 ptr++;
1342 }
1343
1344 if (*ptr)
1345 rule.lines_num = abs(atoi(ptr));
1346
1347 if (rule.lines_num && rule.lines_cmp == FILTER_LINES_NO)
1349
1350 if (rule.lines_cmp != FILTER_LINES_NO && rule.lines_num)
1351 rule.lines_ok = TRUE;
1352
1353 /*
1354 * Scoring value
1355 */
1358
1359 buf[0] = '\0';
1361 rule.comment = free_filter_comment(rule.comment);
1362 return FALSE;
1363 }
1364
1365 /* check if a score has been entered */
1366 if (buf[0] != '\0')
1367 /* use entered score */
1368 rule.score = atoi(buf);
1369 else {
1370 /* use default score */
1371 if (type == GLOBAL_MENU_FILTER_KILL)
1372 rule.score = tinrc.score_kill;
1373 else /* type == GLOBAL_MENU_FILTER_SELECT */
1374 rule.score = tinrc.score_select;
1375 }
1376
1377 if (!rule.score) { /* ignore 0 scores */
1378 rule.comment = free_filter_comment(rule.comment);
1379 return FALSE;
1380 }
1381
1382 /*
1383 * assure we are in range
1384 */
1385 if (rule.score < 0)
1386 rule.score = abs(rule.score);
1387 if (rule.score > SCORE_MAX)
1388 rule.score = SCORE_MAX;
1389
1390 /* get the right sign for the score */
1391 if (type == GLOBAL_MENU_FILTER_KILL)
1392 rule.score = -rule.score;
1393
1394 /*
1395 * Expire time
1396 */
1397 snprintf(double_time, sizeof(double_time), "2x %s", text_time);
1398 snprintf(quat_time, sizeof(quat_time), "4x %s", text_time);
1399 list = my_malloc(sizeof(char *) * 4);
1400 list[0] = (char *) _(txt_unlimited_time);
1401 list[1] = text_time;
1402 list[2] = double_time;
1403 list[3] = quat_time;
1405 free(list);
1406
1407 if (i == -1) {
1408 rule.comment = free_filter_comment(rule.comment);
1409 return FALSE;
1410 }
1411
1412 rule.expire_time = i;
1413
1414 /*
1415 * Scope
1416 */
1417 if (*rule.text || rule.subj_ok || rule.from_ok || rule.msgid_ok || rule.lines_ok) {
1418 int j = 0;
1419
1420 list = my_malloc(sizeof(char *) * 2); /* at least 2 scopes */
1421 list[j++] = my_strdup(group->name);
1422 list[j] = my_strdup(list[j - 1]);
1423 while ((ptr = strrchr(list[j], '.')) != NULL) {
1424 *(++ptr) = '*';
1425 *(++ptr) = '\0';
1426 j++;
1427 list = my_realloc(list, sizeof(char *) * (size_t) (j + 1)); /* one element more */
1428 list[j] = my_strdup(list[j - 1]);
1429 list[j][strlen(list[j]) - 2] = '\0';
1430 }
1431 free(list[j]); /* this copy isn't needed anymore */
1432 list[j] = (char *) _(txt_all_groups);
1433
1434 if ((i = get_choice(INDEX_TOP + 13, ptr_filter_help_scope, ptr_filter_scope, list, j + 1)) > 0)
1435 my_strncpy(rule.scope, i == j ? "*" : list[i], sizeof(rule.scope) - 1);
1436
1437 for (j--; j >= 0; j--)
1438 free(list[j]);
1439 free(list);
1440
1441 if (i == -1) {
1442 rule.comment = free_filter_comment(rule.comment);
1443 return FALSE;
1444 }
1445 } else {
1446 rule.comment = free_filter_comment(rule.comment);
1447 return FALSE;
1448 }
1449
1450 forever {
1451 func = prompt_slk_response(default_func, filter_keys,
1452 ptr_filter_quit_edit_save, keyquit, keyedit, keysave);
1453 switch (func) {
1454
1455 case FILTER_EDIT:
1456 add_filter_rule(group, art, &rule, FALSE); /* save the rule */
1457 rule.comment = free_filter_comment(rule.comment);
1459 return FALSE;
1460 unfilter_articles(group);
1462 return TRUE;
1463 /* keep lint quiet: */
1464 /* FALLTHROUGH */
1465
1466 case GLOBAL_QUIT:
1467 case GLOBAL_ABORT:
1468 rule.comment = free_filter_comment(rule.comment);
1469 return FALSE;
1470 /* keep lint quiet: */
1471 /* FALLTHROUGH */
1472
1473 case FILTER_SAVE:
1474 /*
1475 * Add the filter rule and save it to the filter file
1476 */
1477 ret = add_filter_rule(group, art, &rule, FALSE);
1478 rule.comment = free_filter_comment(rule.comment);
1479 return ret;
1480 /* keep lint quiet: */
1481 /* FALLTHROUGH */
1482
1483 default:
1484 break;
1485 }
1486 }
1487 /* NOTREACHED */
1488 return FALSE;
1489}
1490
1491
1492/*
1493 * Quick command to add an auto-select / kill filter to specified groups filter
1494 */
1495t_bool
1497 t_function type,
1498 struct t_group *group,
1499 struct t_article *art)
1500{
1501 char *scope;
1502 char txt[LEN];
1503 int header, expire, icase;
1504 struct t_filter_rule rule;
1505 t_bool ret;
1506
1507 if (type == GLOBAL_QUICK_FILTER_KILL) {
1508 header = group->attribute->quick_kill_header;
1509 expire = group->attribute->quick_kill_expire;
1510 /* ON=case sensitive, OFF=ignore case -> invert */
1513 } else { /* type == GLOBAL_QUICK_FILTER_SELECT */
1514 header = group->attribute->quick_select_header;
1515 expire = group->attribute->quick_select_expire;
1516 /* ON=case sensitive, OFF=ignore case -> invert */
1519 }
1520
1521#ifdef DEBUG
1522 if (debug & DEBUG_FILTER)
1523 error_message(2, "%s header=[%d] scope=[%s] expire=[%s] case=[%d]", (type == GLOBAL_QUICK_FILTER_KILL) ? "KILL" : "SELECT", header, BlankIfNull(scope), txt_onoff[expire != FALSE ? 1 : 0], icase);
1524#endif /* DEBUG */
1525
1526 /*
1527 * Setup rules
1528 */
1529 if (strlen(BlankIfNull(scope)) > (sizeof(rule.scope) - 1))
1530 return FALSE;
1531 my_strncpy(rule.scope, BlankIfNull(scope), sizeof(rule.scope) - 1);
1532 rule.counter = 0;
1534 rule.lines_num = 0;
1535 rule.lines_ok = (header == FILTER_LINES);
1536 rule.msgid_ok = (header == FILTER_MSGID) || (header == FILTER_MSGID_LAST);
1537 rule.fullref = header; /* value is directly used to select correct filter type */
1538 rule.from_ok = (header == FILTER_FROM_CASE_SENSITIVE || header == FILTER_FROM_CASE_IGNORE);
1539 rule.subj_ok = (header == FILTER_SUBJ_CASE_SENSITIVE || header == FILTER_SUBJ_CASE_IGNORE);
1540
1541 /* create an auto-comment. */
1542 if (type == GLOBAL_QUICK_FILTER_KILL)
1543 snprintf(txt, sizeof(txt), "%s%s%c%s%s%s", _(txt_filter_rule_created), "'", ']', "' (", _(txt_help_article_quick_kill), ").");
1544 else
1545 snprintf(txt, sizeof(txt), "%s%s%c%s%s%s", _(txt_filter_rule_created), "'", '[', "' (", _(txt_help_article_quick_select), ").");
1546 rule.comment = add_filter_comment(NULL, txt);
1547
1548 rule.text[0] = '\0';
1549 rule.icase = icase;
1550 rule.expire_time = expire;
1551 rule.check_string = TRUE;
1553
1554 ret = add_filter_rule(group, art, &rule, TRUE);
1555 rule.comment = free_filter_comment(rule.comment);
1556 return ret;
1557}
1558
1559
1560/*
1561 * Quick command to add an auto-select filter to the article that user
1562 * has just posted. Selects on Subject: line with limited expire time.
1563 * Don't process if GROUP_TYPE_MAIL || GROUP_TYPE_SAVE
1564 */
1565t_bool
1567 struct t_group *group,
1568 const char *subj,
1569 const char *a_message_id) /* return value is always ignored */
1570{
1571 t_bool filtered = FALSE;
1572 char txt[LEN];
1573
1574 if (group->type == GROUP_TYPE_NEWS) {
1575 struct t_article art;
1576 struct t_filter_rule rule;
1577
1578#ifdef __cplusplus /* keep C++ quiet */
1579 rule.scope[0] = '\0';
1580#endif /* __cplusplus */
1581
1582 if (strlen(group->name) > (sizeof(rule.scope) - 1)) /* groupname to long? */
1583 return FALSE;
1584
1585 /*
1586 * Setup rules
1587 */
1588 rule.counter = 0;
1590 rule.lines_num = 0;
1591 rule.from_ok = FALSE;
1592 rule.lines_ok = FALSE;
1593 rule.msgid_ok = FALSE;
1594 rule.fullref = FILTER_MSGID;
1595 rule.subj_ok = TRUE;
1596 rule.text[0] = '\0';
1597 rule.icase = FALSE;
1598 rule.expire_time = TRUE;
1599 rule.check_string = TRUE;
1600 rule.score = tinrc.score_select;
1601
1602 strcpy(rule.scope, group->name);
1603
1604 /* create an auto-comment. */
1605 snprintf(txt, sizeof(txt), "%s%s", _(txt_filter_rule_created), "add_posted_to_filter=ON.");
1606 rule.comment = add_filter_comment(NULL, txt);
1607
1608 /*
1609 * Setup dummy article with posted articles subject
1610 * xor Message-ID
1611 */
1612 set_article(&art);
1613 if (*a_message_id) {
1614 /* initialize art->refptr */
1615 struct {
1616 struct t_msgid *next;
1617 struct t_msgid *parent;
1618 struct t_msgid *sibling;
1619 struct t_msgid *child;
1620 int article;
1621 char txt[HEADER_LEN];
1622 } refptr_dummyart;
1623
1624 rule.subj_ok = FALSE;
1625 rule.msgid_ok = TRUE;
1626 refptr_dummyart.next = (struct t_msgid *) 0;
1627 refptr_dummyart.parent = (struct t_msgid *) 0;
1628 refptr_dummyart.sibling = (struct t_msgid *) 0;
1629 refptr_dummyart.child = (struct t_msgid *) 0;
1630 refptr_dummyart.article = ART_NORMAL;
1631 my_strncpy(refptr_dummyart.txt, a_message_id, HEADER_LEN);
1632 /* Hack */
1633 art.refptr = (struct t_msgid *) &refptr_dummyart;
1634
1635 filtered = add_filter_rule(group, &art, &rule, FALSE);
1636 } else {
1637 art.subject = my_strdup(subj);
1638 filtered = add_filter_rule(group, &art, &rule, FALSE);
1639 FreeIfNeeded(art.subject);
1640 }
1641 rule.comment = free_filter_comment(rule.comment);
1642 }
1643 return filtered;
1644}
1645
1646
1647/*
1648 * API to add filter rule to the local or global filter array
1649 */
1650static t_bool
1652 struct t_group *group,
1653 struct t_article *art,
1654 struct t_filter_rule *rule,
1655 t_bool quick_filter_rule)
1656{
1657 char acbuf[PATH_LEN];
1658 char sbuf[(sizeof(acbuf) / 2)]; /* half as big as acbuf so quote_wild(sbuf) fits into acbuf */
1659 int i = glob_filter.num;
1660 t_bool filtered = FALSE;
1661 time_t current_time;
1662 struct t_filter *ptr;
1663
1666
1667 ptr = glob_filter.filter;
1668
1669 ptr[i].inscope = TRUE;
1670 ptr[i].icase = FALSE;
1671 ptr[i].fullref = FILTER_MSGID;
1672 ptr[i].comment = (struct t_filter_comment *) 0;
1673 ptr[i].scope = NULL;
1674 ptr[i].subj = NULL;
1675 ptr[i].from = NULL;
1676 ptr[i].msgid = NULL;
1677 ptr[i].lines_cmp = (char) rule->lines_cmp;
1678 ptr[i].lines_num = rule->lines_num;
1679 ptr[i].gnksa_cmp = FILTER_LINES_NO;
1680 ptr[i].gnksa_num = 0;
1681 ptr[i].score = rule->score;
1682 ptr[i].xref = NULL;
1683 ptr[i].path = NULL;
1684
1685 if (rule->comment != NULL)
1686 ptr[i].comment = copy_filter_comment(rule->comment, ptr[i].comment);
1687
1688 if (rule->scope[0] == '\0') /* replace empty scope with current group name */
1689 ptr[i].scope = my_strdup(group->name);
1690 else {
1691 if ((rule->scope[0] != '*') && (rule->scope[1] != '\0')) /* copy non-global scope */
1692 ptr[i].scope = my_strdup(rule->scope);
1693 }
1694
1695 (void) time(&current_time);
1696 switch (rule->expire_time) {
1697 case 1:
1698 ptr[i].time = current_time + (time_t) (tinrc.filter_days * DAY);
1699 break;
1700
1701 case 2:
1702 ptr[i].time = current_time + (time_t) (tinrc.filter_days * DAY * 2);
1703 break;
1704
1705 case 3:
1706 ptr[i].time = current_time + (time_t) (tinrc.filter_days * DAY * 4);
1707 break;
1708
1709 default:
1710 ptr[i].time = (time_t) 0;
1711 break;
1712 }
1713
1714 ptr[i].icase = rule->icase;
1715 if (*rule->text) {
1716 snprintf(acbuf, sizeof(acbuf), REGEX_FMT, quote_wild_whitespace(rule->text));
1717
1718 switch (rule->counter) {
1721 ptr[i].subj = my_strdup(acbuf);
1722 break;
1723
1726 ptr[i].from = my_strdup(acbuf);
1727 break;
1728
1729 case FILTER_MSGID:
1730 case FILTER_MSGID_LAST:
1731 case FILTER_MSGID_ONLY:
1732 case FILTER_REFS_ONLY:
1733 ptr[i].msgid = my_strdup(acbuf);
1734 ptr[i].fullref = rule->counter;
1735 break;
1736
1737 default: /* should not happen */
1738 /* CONSTANTCONDITION */
1739 assert(0 != 0);
1740 break;
1741 }
1742 filtered = TRUE;
1743 glob_filter.num++;
1744 } else {
1745 /*
1746 * STRCPY() truncates subject/from/message-id so it fits
1747 * into acbuf even after quote_wild()
1748 */
1749 if (rule->subj_ok) {
1750 STRCPY(sbuf, art->subject);
1751 snprintf(acbuf, sizeof(acbuf), REGEX_FMT, (rule->check_string ? quote_wild(sbuf) : sbuf));
1752 ptr[i].subj = my_strdup(acbuf);
1753 }
1754 if (rule->from_ok) {
1755 STRCPY(sbuf, art->from);
1756 snprintf(acbuf, sizeof(acbuf), REGEX_FMT, quote_wild(sbuf));
1757 ptr[i].from = my_strdup(acbuf);
1758 }
1759 /*
1760 * message-ids should be quoted
1761 */
1762 if (rule->msgid_ok) {
1763 /*
1764 * If threading by references is set, and a quick kill is applied
1765 * (in group level), it is applied with the data of the root
1766 * article of the thread built by tin.
1767 * In case of threading by references, if tin's root is not the
1768 * *real* root of thread (which is the first entry in references
1769 * field) any applying of filtering for MSGID (or MSGID_LAST)
1770 * doesn't work, because the filter is applied with the data of
1771 * tin's root article which doesn't cover the other articles in
1772 * the thread.
1773 * So the thread remains open (in group level). To overcome this,
1774 * the first msgid from references field is taken in this case.
1775 */
1776 if (quick_filter_rule && group->attribute->thread_articles == THREAD_REFS &&
1779 art->refptr->parent != NULL)
1780 {
1781 /* not real parent, take first references entry as MID */
1782 struct t_msgid *xptr;
1783
1784 for (xptr = art->refptr->parent; xptr->parent != NULL; xptr = xptr->parent)
1785 ;
1786 STRCPY(sbuf, xptr->txt);
1787 } else {
1788 STRCPY(sbuf, MSGID(art));
1789 }
1790 snprintf(acbuf, sizeof(acbuf), REGEX_FMT, quote_wild(sbuf));
1791 ptr[i].msgid = my_strdup(acbuf);
1792 ptr[i].fullref = rule->fullref;
1793 }
1794 if (rule->subj_ok || rule->from_ok || rule->msgid_ok || rule->lines_ok) {
1795 filtered = TRUE;
1796 glob_filter.num++;
1797 }
1798 }
1799
1800 if (filtered) {
1801#ifdef DEBUG
1802 if (debug & DEBUG_FILTER)
1803 wait_message(2, "inscope=[%s] scope=[%s] case=[%d] subj=[%s] from=[%s] msgid=[%s] fullref=[%u] line=[%d %d] time=[%lu]", bool_unparse(ptr[i].inscope), BlankIfNull(rule->scope), ptr[i].icase, BlankIfNull(ptr[i].subj), BlankIfNull(ptr[i].from), BlankIfNull(ptr[i].msgid), ptr[i].fullref, ptr[i].lines_cmp, ptr[i].lines_num, (unsigned long int) ptr[i].time);
1804#endif /* DEBUG */
1806 }
1807 return filtered;
1808}
1809
1810
1811/*
1812 * We assume that any articles which are tagged as killed are also
1813 * tagged as being read BECAUSE they were killed. We retag them as
1814 * being unread if they were unread before killing (ART_KILLED_UNREAD).
1815 * Selected articles will be un"select"ed.
1816 */
1817void
1819 struct t_group *group)
1820{
1821 int i;
1822
1823 for_each_art(i) {
1824 arts[i].score = 0;
1825 if (IS_KILLED(i)) {
1826 if (IS_KILLED_UNREAD(i))
1827 art_mark(group, &arts[i], ART_UNREAD);
1829 }
1830 if (IS_SELECTED(i))
1831 arts[i].selected = FALSE;
1832 }
1833}
1834
1835
1836/*
1837 * Filter any articles in specified group.
1838 * Apply global filter rules followed by group filter rules.
1839 * In global rules check if scope field set to determine if
1840 * filter applies to current group.
1841 */
1842t_bool
1844 struct t_group *group)
1845{
1846 char buf[LEN];
1847 int num, inscope;
1848 int i, j;
1849 struct t_filter *ptr;
1850 struct regex_cache *regex_cache_subj = NULL;
1851 struct regex_cache *regex_cache_from = NULL;
1852 struct regex_cache *regex_cache_msgid = NULL;
1853 struct regex_cache *regex_cache_xref = NULL;
1854 struct regex_cache *regex_cache_path = NULL;
1855 t_bool filtered = FALSE;
1856 t_bool error = FALSE;
1857
1858 /*
1859 * check if there are any global filter rules
1860 */
1861 if (group->glob_filter->num == 0)
1862 return filtered;
1863
1864 /*
1865 * Apply global filter rules first if there are any entries
1866 */
1867 /*
1868 * Check if any scope rules are active for this group
1869 * ie. group=comp.os.linux.help scope=comp.os.linux.*
1870 */
1871 inscope = set_filter_scope(group);
1872 if (!cmd_line && !batch_mode)
1873 wait_message(0, _(txt_filter_global_rules), inscope, group->glob_filter->num);
1874 num = group->glob_filter->num;
1875 ptr = group->glob_filter->filter;
1876
1877 /*
1878 * set up cache tables for all types of filter rules
1879 * (only for regexp matching)
1880 */
1881 if (tinrc.wildcard) {
1882 size_t msiz;
1883
1884 msiz = sizeof(struct regex_cache) * (size_t) num;
1885 regex_cache_subj = my_malloc(msiz);
1886 regex_cache_from = my_malloc(msiz);
1887 regex_cache_msgid = my_malloc(msiz);
1888 regex_cache_xref = my_malloc(msiz);
1889 regex_cache_path = my_malloc(msiz);
1890 for (j = 0; j < num; j++) {
1891 regex_cache_subj[j].re = NULL;
1892 regex_cache_subj[j].extra = NULL;
1893 regex_cache_from[j].re = NULL;
1894 regex_cache_from[j].extra = NULL;
1895 regex_cache_msgid[j].re = NULL;
1896 regex_cache_msgid[j].extra = NULL;
1897 regex_cache_xref[j].re = NULL;
1898 regex_cache_xref[j].extra = NULL;
1899 regex_cache_path[j].re = NULL;
1900 regex_cache_path[j].extra = NULL;
1901 }
1902 }
1903
1904 /*
1905 * loop through all arts applying global & local filtering rules
1906 */
1907 for (i = 0; (i < top_art) && !error; i++) {
1908#ifdef HAVE_SELECT
1909 if (wait_for_input()) /* allow abort */
1910 break; /* to free the mem */
1911#endif /* HAVE_SELECT */
1912
1913 arts[i].score = 0;
1914
1915 if (tinrc.kill_level == KILL_UNREAD && IS_READ(i)) /* skip only when the article is read */
1916 continue;
1917
1918 for (j = 0; j < num && !error; j++) {
1919 if (ptr[j].inscope) {
1920 /*
1921 * Filter on Subject: line
1922 */
1923 if (ptr[j].subj != NULL) {
1924 switch (test_regex(arts[i].subject, ptr[j].subj, ptr[j].icase, &regex_cache_subj[j])) {
1925 case 1:
1926 SET_FILTER(group, i, j);
1927 break;
1928
1929 case -1:
1930 error = TRUE;
1931 break;
1932
1933 default:
1934 break;
1935 }
1936 }
1937
1938 /*
1939 * Filter on From: line
1940 */
1941 if (ptr[j].from != NULL) {
1942 if (arts[i].name != NULL)
1943 snprintf(buf, sizeof(buf), "%s (%s)", arts[i].from, arts[i].name);
1944 else
1945 STRCPY(buf, arts[i].from);
1946
1947 switch (test_regex(buf, ptr[j].from, ptr[j].icase, &regex_cache_from[j])) {
1948 case 1:
1949 SET_FILTER(group, i, j);
1950 break;
1951
1952 case -1:
1953 error = TRUE;
1954 break;
1955
1956 default:
1957 break;
1958 }
1959 }
1960
1961 /*
1962 * Filter on Message-ID: line
1963 * Apply to Message-ID: & References: lines or
1964 * Message-ID: & last entry from References: line
1965 * Case is important here
1966 */
1967 if (ptr[j].msgid != NULL) {
1968 char *refs = NULL;
1969 const char *myrefs = NULL;
1970 const char *mymsgid = NULL;
1971 int x;
1972 struct t_article *art = &arts[i];
1973 /*
1974 * TODO: nice idea del'd; better apply one rule on all
1975 * fitting articles, so we can switch to an appropriate
1976 * algorithm for each kind of rule, including the
1977 * deleted one.
1978 */
1979
1980 /* myrefs does not need to be freed */
1981
1982 /* use full references header or just the last entry? */
1983 switch (ptr[j].fullref) {
1984 case FILTER_MSGID:
1985 myrefs = REFS(art, refs);
1986 mymsgid = MSGID(art);
1987 break;
1988
1989 case FILTER_MSGID_LAST:
1990 myrefs = art->refptr ? (art->refptr->parent ? art->refptr->parent->txt : "") : "";
1991 mymsgid = MSGID(art);
1992 break;
1993
1994 case FILTER_MSGID_ONLY:
1995 myrefs = "";
1996 mymsgid = MSGID(art);
1997 break;
1998
1999 case FILTER_REFS_ONLY:
2000 myrefs = REFS(art, refs);
2001 mymsgid = "";
2002 break;
2003
2004 default: /* should not happen */
2005 /* CONSTANTCONDITION */
2006 assert(0 != 0);
2007 break;
2008 }
2009
2010 if ((x = test_regex(myrefs, ptr[j].msgid, FALSE, &regex_cache_msgid[j])) == 0) /* no match */
2011 x = test_regex(mymsgid, ptr[j].msgid, FALSE, &regex_cache_msgid[j]);
2012
2013 switch (x) {
2014 case 1:
2015 SET_FILTER(group, i, j);
2016#ifdef DEBUG
2017 if (debug & DEBUG_FILTER)
2018 debug_print_file("FILTER", "Group[%s] Rule[%d][%s] ArtMSGID[%s] Artnum[%d]", group->name, j, ptr[j].msgid, mymsgid, i);
2019#endif /* DEBUG */
2020 break;
2021
2022 case -1:
2023 error = TRUE;
2024 break;
2025
2026 default:
2027 break;
2028 }
2030 }
2031 /*
2032 * Filter on Lines: line
2033 */
2034 if ((ptr[j].lines_cmp != FILTER_LINES_NO) && (arts[i].line_count >= 0)) {
2035 switch (ptr[j].lines_cmp) {
2036 case FILTER_LINES_EQ:
2037 if (arts[i].line_count == ptr[j].lines_num) {
2038 SET_FILTER(group, i, j);
2039 }
2040 break;
2041
2042 case FILTER_LINES_LT:
2043 if (arts[i].line_count < ptr[j].lines_num) {
2044 SET_FILTER(group, i, j);
2045 }
2046 break;
2047
2048 case FILTER_LINES_GT:
2049 if (arts[i].line_count > ptr[j].lines_num) {
2050 SET_FILTER(group, i, j);
2051 }
2052 break;
2053
2054 default:
2055 break;
2056 }
2057 }
2058
2059 /*
2060 * Filter on GNKSA code
2061 */
2062 if ((ptr[j].gnksa_cmp != FILTER_LINES_NO) && (arts[i].gnksa_code >= 0)) {
2063 switch (ptr[j].gnksa_cmp) {
2064 case FILTER_LINES_EQ:
2065 if (arts[i].gnksa_code == ptr[j].gnksa_num) {
2066 SET_FILTER(group, i, j);
2067 }
2068 break;
2069
2070 case FILTER_LINES_LT:
2071 if (arts[i].gnksa_code < ptr[j].gnksa_num) {
2072 SET_FILTER(group, i, j);
2073 }
2074 break;
2075
2076 case FILTER_LINES_GT:
2077 if (arts[i].gnksa_code > ptr[j].gnksa_num) {
2078 SET_FILTER(group, i, j);
2079 }
2080 break;
2081
2082 default:
2083 break;
2084 }
2085 }
2086
2087 /*
2088 * Filter on Xref: lines
2089 *
2090 * "news.foo.bar foo.bar:666 bar.bar:999"
2091 * is turned into the Newsgroups like string
2092 * foo.bar,bar.bar
2093 */
2094 if (arts[i].xref && *arts[i].xref) {
2095 if (ptr[j].score && ptr[j].xref != NULL) {
2096 char *s, *e, *k;
2097 t_bool skip = FALSE;
2098
2099 s = arts[i].xref;
2100 if (strchr(s, ' ') || strchr(s, '\t')) {
2101 while (*s && !isspace((int) *s)) /* skip server name */
2102 s++;
2103 while (*s && isspace((int) *s))
2104 s++;
2105 }
2106#ifdef DEBUG
2107 else { /* server name missing in overview, i.e. colobus 2.1 */
2108 if (debug & DEBUG_FILTER) { /* TODO: lang.c, _()? */
2109 debug_print_file("FILTER", "Malformed overview entry: servername missing.");
2110 debug_print_file("FILTER", "\t Xref: %s", arts[i].xref);
2111 }
2112 }
2113#endif /* DEBUG */
2114 if (strlen(s)) {
2115 /* reformat */
2116 k = e = my_malloc(strlen(s) + 1);
2117 while (*s) {
2118 if (*s == ':') {
2119 *e++ = ',';
2120 skip = TRUE;
2121 }
2122 if (*s != ':' && !isspace((int) *s) && !skip)
2123 *e++ = *s;
2124 if (isspace((int) *s))
2125 skip = FALSE;
2126 s++;
2127 }
2128 *--e = '\0';
2129 } else {
2130#ifdef DEBUG
2131 if (debug & DEBUG_FILTER) /* TODO: lang.c, _()? */
2132 debug_print_file("FILTER", "Skipping Xref filter");
2133#endif /* DEBUG */
2134 error = TRUE;
2135 break;
2136 }
2137
2138 switch (test_regex(k, ptr[j].xref, ptr[j].icase, &regex_cache_xref[j])) {
2139 case 1:
2140 SET_FILTER(group, i, j);
2141 break;
2142
2143 case -1:
2144 error = TRUE;
2145 break;
2146
2147 default:
2148 break;
2149 }
2150 free(k);
2151 }
2152 }
2153
2154 /*
2155 * Filter on Path: lines
2156 */
2157 if (arts[i].path && *arts[i].path) {
2158 if (ptr[j].path != NULL) {
2159 switch (test_regex(arts[i].path, ptr[j].path, ptr[j].icase, &regex_cache_path[j])) {
2160 case 1:
2161 SET_FILTER(group, i, j);
2162 break;
2163
2164 case -1:
2165 error = TRUE;
2166 break;
2167
2168 default:
2169 break;
2170 }
2171 }
2172 }
2173 }
2174 }
2175 /*
2176 * if (i % (MODULO_COUNT_NUM * 20) == 0)
2177 * show_progress("Filter", i, top_art);
2178 */
2179 }
2180
2181 /*
2182 * throw away the contents of all regex_caches
2183 */
2184 if (tinrc.wildcard) {
2185 for (j = 0; j < num; j++) {
2186 FreeIfNeeded(regex_cache_subj[j].re);
2187 FreeIfNeeded(regex_cache_subj[j].extra);
2188 FreeIfNeeded(regex_cache_from[j].re);
2189 FreeIfNeeded(regex_cache_from[j].extra);
2190 FreeIfNeeded(regex_cache_msgid[j].re);
2191 FreeIfNeeded(regex_cache_msgid[j].extra);
2192 FreeIfNeeded(regex_cache_xref[j].re);
2193 FreeIfNeeded(regex_cache_xref[j].extra);
2194 FreeIfNeeded(regex_cache_path[j].re);
2195 FreeIfNeeded(regex_cache_path[j].extra);
2196 }
2197 free(regex_cache_subj);
2198 free(regex_cache_from);
2199 free(regex_cache_msgid);
2200 free(regex_cache_xref);
2201 free(regex_cache_path);
2202 }
2203
2204 /*
2205 * now entering the main filter loop:
2206 * all articles have scored, so do kill & select
2207 */
2208 if (!error) {
2209 for_each_art(i) {
2210 if (arts[i].score <= tinrc.score_limit_kill) {
2211 if (arts[i].status == ART_UNREAD)
2213 else
2214 arts[i].killed = ART_KILLED;
2215 filtered = TRUE;
2216 art_mark(group, &arts[i], ART_READ);
2217 if (group->attribute->show_only_unread_arts)
2218 arts[i].keep_in_base = FALSE;
2219 } else if (arts[i].score >= tinrc.score_limit_select) {
2220 arts[i].selected = TRUE;
2221 }
2222 }
2223 }
2224
2225 if (!cmd_line && !batch_mode)
2226 clear_message();
2227
2228 return filtered;
2229}
2230
2231
2232/*
2233 * Check if we have to filter on Path: for the given group
2234 */
2235t_bool
2237 struct t_group *group)
2238{
2239 int i;
2240 struct t_filter *flt;
2241 t_bool ret = FALSE;
2242
2243 if (group->glob_filter->num == 0)
2244 return ret;
2245
2246 if (set_filter_scope(group)) {
2247 flt = group->glob_filter->filter;
2248 for (i = 0; i < group->glob_filter->num; i++) {
2249 if (flt[i].inscope && flt[i].path) {
2250 ret = TRUE;
2251 break;
2252 }
2253 }
2254 }
2255 return ret;
2256}
2257
2258
2259static int
2261 struct t_group *group)
2262{
2263 int i, num, inscope;
2264 struct t_filter *ptr, *prev;
2265
2266 inscope = num = group->glob_filter->num;
2267 prev = ptr = group->glob_filter->filter;
2268
2269 for (i = 0; i < num; i++) {
2270 ptr[i].inscope = TRUE;
2271 ptr[i].next = (struct t_filter *) 0;
2272 if (ptr[i].scope != NULL) {
2273 if (!match_group_list(group->name, ptr[i].scope)) {
2274 ptr[i].inscope = FALSE;
2275 inscope--;
2276 }
2277 }
2278 if (i != 0 && ptr[i].inscope)
2279 prev = prev->next = &ptr[i];
2280 }
2281 return inscope;
2282}
2283
2284
2285/*
2286 * This will come in useful for filtering on non-overview hdr fields
2287 */
2288#if 0
2289static FILE *
2290open_xhdr_fp(
2291 char *header,
2292 long min,
2293 long max)
2294{
2295# ifdef NNTP_ABLE
2297 char buf[NNTP_STRLEN];
2298
2299 snprintf(buf, sizeof(buf), "%s %s %ld-%ld", nntp_caps.hdr_cmd, header, min, max);
2300 return (nntp_command(buf, OK_HEAD, NULL, 0));
2301 } else
2302# endif /* NNTP_ABLE */
2303 return (FILE *) 0; /* Some trick implementation for local spool... */
2304}
2305#endif /* 0 */
unsigned t_bool
Definition: bool.h:77
#define bool_not(b)
Definition: bool.h:81
#define TRUE
Definition: bool.h:74
#define FALSE
Definition: bool.h:70
#define bool_unparse(b)
Definition: bool.h:83
static t_openartinfo * art
Definition: cook.c:78
#define DEBUG_FILTER
Definition: debug.h:49
constext txt_kill_subj[]
Definition: lang.c:602
constext txt_help_filter_msgid[]
Definition: lang.c:332
int verbose
Definition: init.c:154
constext txt_pcre_error_num[]
Definition: lang.c:727
constext txt_unlimited_time[]
Definition: lang.c:910
constext txt_filesystem_full_backup[]
Definition: lang.c:276
constext txt_msgid_line_only[]
Definition: lang.c:665
constext * txt_onoff[]
Definition: lang.c:1270
struct t_capabilities nntp_caps
Definition: init.c:513
constext txt_refs_line_only[]
Definition: lang.c:784
t_bool cmd_line
Definition: init.c:129
t_bool read_saved_news
Definition: init.c:152
constext txt_full[]
Definition: lang.c:310
constext txt_select_from[]
Definition: lang.c:801
struct t_article * arts
Definition: memory.c:69
constext txt_filter_global_rules[]
Definition: lang.c:277
constext txt_filter_file[]
Definition: lang.c:284
constext txt_kill_lines[]
Definition: lang.c:598
constext txt_from_line_only_case[]
Definition: lang.c:314
constext txt_help_filter_time[]
Definition: lang.c:336
constext txt_subj_line_only_case[]
Definition: lang.c:817
constext txt_help_article_quick_kill[]
Definition: lang.c:362
constext txt_select_subj[]
Definition: lang.c:806
constext txt_help_select_scope[]
Definition: lang.c:338
constext txt_msgid_refs_line[]
Definition: lang.c:666
constext txt_help_filter_subj[]
Definition: lang.c:333
constext txt_yes[]
Definition: lang.c:1004
constext txt_help_article_quick_select[]
Definition: lang.c:363
constext txt_quit_edit_save_select[]
Definition: lang.c:765
constext txt_help_filter_from[]
Definition: lang.c:330
constext txt_filter_text_type[]
Definition: lang.c:312
constext txt_select_text[]
Definition: lang.c:807
char filter_file[PATH_LEN]
Definition: init.c:89
constext txt_filter_score[]
Definition: lang.c:308
constext txt_time_default_days[]
Definition: lang.c:870
int signal_context
Definition: signal.c:105
constext txt_filesystem_full[]
Definition: lang.c:275
constext txt_select_msgid[]
Definition: lang.c:804
constext txt_help_filter_lines[]
Definition: lang.c:331
constext txt_help_filter_text[]
Definition: lang.c:334
constext txt_select_time[]
Definition: lang.c:808
constext txt_kill_msgid[]
Definition: lang.c:600
constext txt_filter_score_help[]
Definition: lang.c:309
constext txt_help_kill_scope[]
Definition: lang.c:337
constext txt_kill_menu[]
Definition: lang.c:599
int cCOLS
Definition: curses.c:53
constext txt_select_scope[]
Definition: lang.c:805
constext txt_no[]
Definition: lang.c:677
constext txt_kill_from[]
Definition: lang.c:597
constext txt_only[]
Definition: lang.c:720
constext txt_kill_scope[]
Definition: lang.c:601
int top_art
Definition: art.c:60
constext txt_select_lines[]
Definition: lang.c:802
constext txt_help_filter_text_type[]
Definition: lang.c:335
constext txt_msgid_line_last[]
Definition: lang.c:664
constext txt_filter_comment[]
Definition: lang.c:311
struct t_config tinrc
Definition: init.c:192
unsigned short debug
Definition: debug.c:51
constext txt_subj_line_only[]
Definition: lang.c:816
constext txt_from_line_only[]
Definition: lang.c:313
constext txt_reading_filter_file[]
Definition: lang.c:776
t_bool no_write
Definition: init.c:145
constext txt_last[]
Definition: lang.c:606
constext txt_removed_rule[]
Definition: lang.c:789
constext txt_kill_time[]
Definition: lang.c:604
t_bool read_news_via_nntp
Definition: init.c:151
constext txt_kill_text[]
Definition: lang.c:603
constext txt_quit_edit_save_kill[]
Definition: lang.c:764
constext txt_select_menu[]
Definition: lang.c:803
constext txt_all_groups[]
Definition: lang.c:51
constext txt_help_filter_comment[]
Definition: lang.c:329
constext txt_filter_rule_created[]
Definition: lang.c:278
t_bool batch_mode
Definition: init.c:127
struct t_filters glob_filter
Definition: filter.c:87
static char text_from[255]
Definition: filter.c:968
static const char * ptr_filter_groupname
Definition: filter.c:966
static const char * ptr_filter_text
Definition: filter.c:964
t_bool filter_on_path(struct t_group *group)
Definition: filter.c:2236
#define MSGID(x)
Definition: filter.c:81
static t_bool add_filter_rule(struct t_group *group, struct t_article *art, struct t_filter_rule *rule, t_bool quick_filter_rule)
Definition: filter.c:1651
static char text_subj[255]
Definition: filter.c:967
t_bool filter_articles(struct t_group *group)
Definition: filter.c:1843
static struct t_filter_comment * add_filter_comment(struct t_filter_comment *ptr, char *text)
Definition: filter.c:122
t_bool read_filter_file(const char *file)
Definition: filter.c:308
static int get_choice(int x, const char *help, const char *prompt, char *list[], int list_size)
Definition: filter.c:896
static void expand_filter_array(struct t_filters *ptr)
Definition: filter.c:178
static void write_filter_array(FILE *fp, struct t_filters *ptr)
Definition: filter.c:758
#define IS_KILLED(i)
Definition: filter.c:53
t_bool filter_menu(t_function type, struct t_group *group, struct t_article *art)
Definition: filter.c:1061
static const char * ptr_filter_time
Definition: filter.c:965
static const char * ptr_filter_scope
Definition: filter.c:963
void free_filter_array(struct t_filters *ptr)
Definition: filter.c:288
int filter_file_offset
Definition: filter.c:93
static const char * ptr_filter_lines
Definition: filter.c:961
#define REFS(x, y)
Definition: filter.c:82
static struct t_filter_comment * copy_filter_comment(struct t_filter_comment *from, struct t_filter_comment *to)
Definition: filter.c:163
static struct t_filter_comment * free_filter_comment(struct t_filter_comment *ptr)
Definition: filter.c:142
void write_filter_file(const char *filename)
Definition: filter.c:681
static int test_regex(const char *string, char *regex, t_bool nocase, struct regex_cache *cache)
Definition: filter.c:203
static int set_filter_scope(struct t_group *group)
Definition: filter.c:2260
void unfilter_articles(struct t_group *group)
Definition: filter.c:1818
#define SET_FILTER(grp, i, j)
Definition: filter.c:68
#define IS_KILLED_UNREAD(i)
Definition: filter.c:54
#define IS_SELECTED(i)
Definition: filter.c:55
static void set_filter(struct t_filter *ptr)
Definition: filter.c:242
#define IS_READ(i)
Definition: filter.c:52
static char text_score[255]
Definition: filter.c:970
t_bool quick_filter_select_posted_art(struct t_group *group, const char *subj, const char *a_message_id)
Definition: filter.c:1566
static const char * ptr_filter_menu
Definition: filter.c:962
static const char * ptr_filter_comment
Definition: filter.c:960
static void free_filter_item(struct t_filter *ptr)
Definition: filter.c:271
static void fmt_filter_menu_prompt(char *dest, size_t dest_len, const char *fmt_str, int len, const char *text)
Definition: filter.c:1024
static char text_msgid[255]
Definition: filter.c:969
static void print_filter_menu(void)
Definition: filter.c:974
t_bool quick_filter(t_function type, struct t_group *group, struct t_article *art)
Definition: filter.c:1496
#define MAXKEYLEN
Definition: keymap.h:136
t_function prompt_slk_response(t_function default_func, const struct keylist keys, const char *fmt,...)
Definition: prompt.c:699
#define ESC
Definition: keymap.h:140
@ FILTER_SAVE
Definition: keymap.h:185
@ FILTER_EDIT
Definition: keymap.h:184
@ GLOBAL_ABORT
Definition: keymap.h:186
@ GLOBAL_QUIT
Definition: keymap.h:210
@ GLOBAL_MENU_FILTER_KILL
Definition: keymap.h:197
@ GLOBAL_QUICK_FILTER_KILL
Definition: keymap.h:208
struct keylist filter_keys
Definition: keymap.c:66
enum defined_functions t_function
Definition: keymap.h:375
#define PrintFuncKey(buf, func, keys)
Definition: keymap.h:445
#define iKeyAbort
Definition: keymap.h:143
static char buf[16]
Definition: langinfo.c:50
#define NNTP_STRLEN
Definition: nntplib.h:155
#define OK_HEAD
Definition: nntplib.h:100
t_bool match_integer(char *line, const char *pat, int *dst, int maxval)
Definition: config.c:1586
void center_line(int line, t_bool inverse, const char *str)
Definition: screen.c:298
int ReadCh(void)
Definition: curses.c:1110
int my_tolower(int)
Definition: string.c:261
char * str_trim(char *string)
Definition: string.c:539
char * quote_wild_whitespace(char *str)
Definition: misc.c:2362
void set_article(struct t_article *art)
Definition: art.c:3152
int atoi(const char *s)
size_t my_strftime(char *s, size_t maxsize, const char *format, struct tm *timeptr)
Definition: strftime.c:64
void error_message(unsigned int sdelay, const char *fmt,...)
Definition: screen.c:224
void art_mark(struct t_group *group, struct t_article *art, int flag)
Definition: newsrc.c:1611
t_bool match_group_list(const char *group, const char *group_list)
Definition: active.c:1117
void ClearScreen(void)
Definition: curses.c:410
struct t_version * check_upgrade(char *line, const char *skip, const char *version)
Definition: version.c:65
t_bool prompt_menu_string(int line, const char *prompt, char *var)
Definition: prompt.c:135
t_bool backup_file(const char *filename, const char *backupname)
Definition: misc.c:213
char * my_strdup(const char *str)
Definition: string.c:139
t_bool invoke_editor(const char *filename, int lineno, struct t_group *group)
Definition: misc.c:377
void MoveCursor(int row, int col)
Definition: curses.c:441
void rename_file(const char *old_filename, const char *new_filename)
Definition: misc.c:742
t_bool match_long(char *line, const char *pat, long *dst)
Definition: config.c:1610
t_bool match_string(char *line, const char *pat, char *dst, size_t dstlen)
Definition: config.c:1656
void clear_message(void)
Definition: screen.c:283
char * quote_wild(char *str)
Definition: misc.c:2326
int strwidth(const char *str)
Definition: string.c:1050
void show_menu_help(const char *help_message)
Definition: options_menu.c:833
void wait_message(unsigned int sdelay, const char *fmt,...)
Definition: screen.c:133
void my_strncpy(char *p, const char *q, size_t n)
Definition: string.c:196
t_bool wildmat(const char *text, char *p, t_bool icase)
Definition: wildmat.c:128
t_bool compile_regex(const char *regex, struct regex_cache *cache, int options)
Definition: regex.c:111
int strncasecmp(const char *p, const char *q, size_t n)
Definition: string.c:491
void upgrade_prompt_quit(struct t_version *upgrade, const char *file)
Definition: version.c:121
char * get_tmpfilename(const char *filename)
Definition: misc.c:101
void CleartoEOLN(void)
Definition: curses.c:458
int get_arrow_key(int prech)
Definition: curses.c:961
void(* func)(SIG_ARGS)
Definition: signal.c:176
const char * name
Definition: signal.c:117
pcre_extra * extra
Definition: tin.h:1963
pcre * re
Definition: tin.h:1962
Definition: tin.h:1533
char * xref
Definition: tin.h:1538
char * refs
Definition: tin.h:1542
int score
Definition: tin.h:1550
int line_count
Definition: tin.h:1545
t_bool keep_in_base
Definition: tin.h:1558
int gnksa_code
Definition: tin.h:1546
char * path
Definition: tin.h:1539
unsigned int killed
Definition: tin.h:1552
char * msgid
Definition: tin.h:1541
t_bool selected
Definition: tin.h:1555
unsigned int status
Definition: tin.h:1551
unsigned quick_select_expire
Definition: tin.h:1649
unsigned quick_kill_header
Definition: tin.h:1645
unsigned thread_articles
Definition: tin.h:1677
unsigned quick_kill_expire
Definition: tin.h:1646
char * quick_kill_scope
Definition: tin.h:1624
unsigned quick_kill_case
Definition: tin.h:1647
char * quick_select_scope
Definition: tin.h:1625
unsigned quick_select_header
Definition: tin.h:1648
unsigned quick_select_case
Definition: tin.h:1650
unsigned show_only_unread_arts
Definition: tin.h:1674
const char * hdr_cmd
Definition: nntplib.h:207
int filter_days
Definition: tinrc.h:142
int kill_level
Definition: tinrc.h:151
int score_limit_kill
Definition: tinrc.h:168
int score_kill
Definition: tinrc.h:170
int wildcard
Definition: tinrc.h:167
int score_limit_select
Definition: tinrc.h:169
int score_select
Definition: tinrc.h:171
struct t_filter_comment * next
Definition: tin.h:1910
char * text
Definition: tin.h:1909
t_bool check_string
Definition: tin.h:1954
t_bool subj_ok
Definition: tin.h:1953
int lines_cmp
Definition: tin.h:1946
t_bool lines_ok
Definition: tin.h:1951
char scope[255]
Definition: tin.h:1942
int score
Definition: tin.h:1948
int expire_time
Definition: tin.h:1949
t_bool from_ok
Definition: tin.h:1950
char text[255]
Definition: tin.h:1941
t_bool msgid_ok
Definition: tin.h:1952
int counter
Definition: tin.h:1943
struct t_filter_comment * comment
Definition: tin.h:1940
int icase
Definition: tin.h:1944
int lines_num
Definition: tin.h:1947
int fullref
Definition: tin.h:1945
Definition: tin.h:1916
char * path
Definition: tin.h:1923
unsigned int inscope
Definition: tin.h:1931
char * msgid
Definition: tin.h:1921
int gnksa_num
Definition: tin.h:1927
char lines_cmp
Definition: tin.h:1929
struct t_filter_comment * comment
Definition: tin.h:1917
time_t time
Definition: tin.h:1925
struct t_filter * next
Definition: tin.h:1924
unsigned int icase
Definition: tin.h:1932
unsigned int fullref
Definition: tin.h:1933
int score
Definition: tin.h:1928
char * scope
Definition: tin.h:1918
char * subj
Definition: tin.h:1919
int lines_num
Definition: tin.h:1926
char gnksa_cmp
Definition: tin.h:1930
char * from
Definition: tin.h:1920
char * xref
Definition: tin.h:1922
int max
Definition: tin.h:1900
struct t_filter * filter
Definition: tin.h:1902
int num
Definition: tin.h:1901
Definition: tin.h:1816
struct t_filters * glob_filter
Definition: tin.h:1835
struct t_attribute * attribute
Definition: tin.h:1834
unsigned int type
Definition: tin.h:1825
char * name
Definition: tin.h:1817
Definition: tin.h:1509
struct t_msgid * next
Definition: tin.h:1510
struct t_msgid * sibling
Definition: tin.h:1512
struct t_msgid * child
Definition: tin.h:1513
struct t_msgid * parent
Definition: tin.h:1511
int article
Definition: tin.h:1514
char txt[1]
Definition: tin.h:1515
int file_version
Definition: tin.h:2543
enum rc_state state
Definition: tin.h:2542
#define my_flush()
Definition: tcurses.h:177
#define cCRLF
Definition: tcurses.h:156
#define my_fputs(str, stream)
Definition: tcurses.h:159
#define my_printf
Definition: tcurses.h:175
#define FILTER_LINES_GT
Definition: tin.h:1391
#define SCORE_MAX
Definition: tin.h:1376
#define FILTER_FROM_CASE_SENSITIVE
Definition: tin.h:1380
#define LEN
Definition: tin.h:860
#define ART_NOTKILLED
Definition: tin.h:1353
#define SEEK_SET
Definition: tin.h:2512
#define KILL_UNREAD
Definition: tin.h:1217
#define FILTER_MSGID_LAST
Definition: tin.h:1383
@ cFilter
Definition: tin.h:107
#define ART_KILLED_UNREAD
Definition: tin.h:1355
#define STRCPY(dst, src)
Definition: tin.h:820
#define for_each_art(x)
Definition: tin.h:2260
#define KEYMAP_UP
Definition: tin.h:1077
#define DAY
Definition: tin.h:870
#define EOF
Definition: tin.h:2516
#define my_malloc(size)
Definition: tin.h:2245
#define unlink(file)
Definition: tin.h:387
#define ART_KILLED
Definition: tin.h:1354
#define FreeIfNeeded(p)
Definition: tin.h:2252
#define ART_NORMAL
Definition: tin.h:1340
@ RC_IGNORE
Definition: tin.h:110
@ RC_UPGRADE
Definition: tin.h:110
#define FILTER_FROM_CASE_IGNORE
Definition: tin.h:1381
#define REGEX_FMT
Definition: tin.h:1027
#define _(Text)
Definition: tin.h:94
#define forever
Definition: tin.h:816
#define FILTER_REFS_ONLY
Definition: tin.h:1385
#define FILTER_SUBJ_CASE_IGNORE
Definition: tin.h:1379
#define PATH_LEN
Definition: tin.h:843
#define snprintf
Definition: tin.h:2464
#define FreeAndNull(p)
Definition: tin.h:2253
#define THREAD_REFS
Definition: tin.h:1141
#define FILTER_SUBJ_CASE_SENSITIVE
Definition: tin.h:1378
#define GROUP_TYPE_NEWS
Definition: tin.h:1070
#define FILTER_LINES_NO
Definition: tin.h:1388
#define HEADER_LEN
Definition: tin.h:863
#define KEYMAP_DOWN
Definition: tin.h:1078
#define INDEX_TOP
Definition: tin.h:1019
#define FILTER_MSGID_ONLY
Definition: tin.h:1384
#define ART_READ
Definition: tin.h:1345
#define my_realloc(ptr, size)
Definition: tin.h:2247
#define FILTER_LINES_EQ
Definition: tin.h:1389
#define FILTER_LINES_LT
Definition: tin.h:1390
#define FILTER_LINES
Definition: tin.h:1386
#define assert(p)
Definition: tin.h:1320
#define ART_UNREAD
Definition: tin.h:1346
#define FILTER_MSGID
Definition: tin.h:1382
#define MAX(a, b)
Definition: tin.h:808
#define BlankIfNull(p)
Definition: tin.h:2255
#define FILTER_VERSION
Definition: version.h:53