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