"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.1/src/filter.c" (12 Oct 2016, 57756 Bytes) of archive /linux/misc/tin-2.4.1.tar.gz:


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.0_vs_2.4.1.

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