"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.4/src/filter.c" (20 Nov 2019, 59775 Bytes) of package /linux/misc/tin-2.4.4.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "filter.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.4.3_vs_2.4.4.

    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  */
   93 int filter_file_offset;
   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 *
  122 add_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 *
  142 free_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 *
  163 copy_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
  178 expand_filter_array(
  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
  203 test_regex(
  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
  242 set_filter(
  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
  271 free_filter_item(
  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
  288 free_filter_array(
  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
  308 read_filter_file(
  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)
  339         wait_message(0, _(txt_reading_filter_file));
  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)
  347         free_filter_array(&glob_filter);
  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')
  356                 filter_file_offset++;
  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 */
  403                     if (glob_filter.num >= glob_filter.max)
  404                         expand_filter_array(&glob_filter);
  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
  674 write_filter_file(
  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)) {
  689         error_message(2, _(txt_filesystem_full_backup), filename);
  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')
  719                 filter_file_offset++;
  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      */
  734     write_filter_array(fp, &glob_filter);
  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
  751 write_filter_array(
  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
  889 get_choice(
  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)
  899         show_menu_help(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
  967 print_filter_menu(
  968     void)
  969 {
  970     ClearScreen();
  971 
  972     center_line(0, TRUE, ptr_filter_menu);
  973 
  974     MoveCursor(INDEX_TOP, 0);
  975     my_printf("%s%s%s", ptr_filter_comment, cCRLF, cCRLF);
  976     my_printf("%s%s", ptr_filter_text, cCRLF);
  977     my_printf("%s%s%s", _(txt_filter_text_type), cCRLF, 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);
  984     my_printf("%s%s", ptr_filter_scope, ptr_filter_groupname);
  985     my_flush();
  986 }
  987 
  988 
  989 #if defined(SIGWINCH) || defined(SIGTSTP)
  990 void
  991 refresh_filter_menu(
  992     void)
  993 {
  994     print_filter_menu();
  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
 1017 fmt_filter_menu_prompt(
 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
 1054 filter_menu(
 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 
 1078     signal_context = cFilter;
 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      */
 1101     printascii(keyedit, func_to_key(FILTER_EDIT, filter_keys));
 1102     printascii(keyquit, func_to_key(GLOBAL_QUIT, filter_keys));
 1103     printascii(keysave, func_to_key(FILTER_SAVE, filter_keys));
 1104 
 1105     if (type == GLOBAL_MENU_FILTER_KILL) {
 1106         ptr_filter_from = _(txt_kill_from);
 1107         ptr_filter_lines = _(txt_kill_lines);
 1108         ptr_filter_menu = _(txt_kill_menu);
 1109         ptr_filter_msgid = _(txt_kill_msgid);
 1110         ptr_filter_scope = _(txt_kill_scope);
 1111         ptr_filter_subj = _(txt_kill_subj);
 1112         ptr_filter_text = _(txt_kill_text);
 1113         ptr_filter_time = _(txt_kill_time);
 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);
 1118         ptr_filter_lines = _(txt_select_lines);
 1119         ptr_filter_menu = _(txt_select_menu);
 1120         ptr_filter_msgid = _(txt_select_msgid);
 1121         ptr_filter_scope = _(txt_select_scope);
 1122         ptr_filter_subj = _(txt_select_subj);
 1123         ptr_filter_text = _(txt_select_text);
 1124         ptr_filter_time = _(txt_select_time);
 1125         ptr_filter_help_scope = _(txt_help_select_scope);
 1126         ptr_filter_quit_edit_save = _(txt_quit_edit_save_select);
 1127     }
 1128 
 1129     ptr_filter_comment = _(txt_filter_comment);
 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);
 1146     snprintf(text_score, sizeof(text_score), _(txt_filter_score), (type == GLOBAL_MENU_FILTER_KILL ? -tinrc.score_kill : tinrc.score_select));
 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 
 1150     print_filter_menu();
 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      */
 1157     show_menu_help(_(txt_help_filter_comment));
 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      */
 1170     show_menu_help(_(txt_help_filter_text));
 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 
 1187         i = get_choice(INDEX_TOP + 3, _(txt_help_filter_text_type), _(txt_filter_text_type), list, 8);
 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) {
 1197             case FILTER_SUBJ_CASE_IGNORE:
 1198             case FILTER_FROM_CASE_IGNORE:
 1199                 rule.icase = TRUE;
 1200                 break;
 1201 
 1202             case FILTER_SUBJ_CASE_SENSITIVE:
 1203             case FILTER_FROM_CASE_SENSITIVE:
 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      */
 1310     show_menu_help(_(txt_help_filter_lines));
 1311 
 1312     buf[0] = '\0';
 1313 
 1314     if (!prompt_menu_string(INDEX_TOP + 9, ptr_filter_lines, buf)) {
 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      */
 1349     snprintf(buf, sizeof(buf), _(txt_filter_score_help), SCORE_MAX);
 1350     show_menu_help(buf);
 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;
 1397     i = get_choice(INDEX_TOP + 11, _(txt_help_filter_time), ptr_filter_time, list, 4);
 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);
 1451             if (!invoke_editor(filter_file, filter_file_offset, NULL))
 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
 1489 quick_filter(
 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 */
 1504         icase = bool_not(group->attribute->quick_kill_case);
 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 */
 1510         icase = bool_not(group->attribute->quick_select_case);
 1511         scope = group->attribute->quick_select_scope;
 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;
 1545     rule.score = (type == GLOBAL_QUICK_FILTER_KILL) ? tinrc.score_kill : tinrc.score_select;
 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
 1559 quick_filter_select_posted_art(
 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
 1644 add_filter_rule(
 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)
 1658         expand_filter_array(&glob_filter);
 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) {
 1712             case FILTER_SUBJ_CASE_IGNORE:
 1713             case FILTER_SUBJ_CASE_SENSITIVE:
 1714                 ptr[i].subj = my_strdup(acbuf);
 1715                 break;
 1716 
 1717             case FILTER_FROM_CASE_IGNORE:
 1718             case FILTER_FROM_CASE_SENSITIVE:
 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 &&
 1770                 (group->attribute->quick_kill_header == FILTER_MSGID ||
 1771                  group->attribute->quick_kill_header == FILTER_REFS_ONLY) &&
 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 */
 1798         write_filter_file(filter_file);
 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
 1811 unfilter_articles(
 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
 1836 filter_articles(
 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)
 2200                     arts[i].killed = ART_KILLED_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
 2223 filter_on_path(
 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
 2247 set_filter_scope(
 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
 2283     if (read_news_via_nntp && !read_saved_news && nntp_caps.hdr_cmd) {
 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 */