"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.2/src/filter.c" (8 Dec 2017, 57746 Bytes) of package /linux/misc/tin-2.4.2.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.1_vs_2.4.2.

    1 /*
    2  *  Project   : tin - a Usenet reader
    3  *  Module    : filter.c
    4  *  Author    : I. Lea
    5  *  Created   : 1992-12-28
    6  *  Updated   : 2017-03-28
    7  *  Notes     : Filter articles. Kill & auto selection are supported.
    8  *
    9  * Copyright (c) 1991-2018 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 
 1016 
 1017 /*
 1018  * Interactive filter menu so that the user can dynamically enter parameters.
 1019  * Can be configured for kill or auto-selection screens.
 1020  */
 1021 t_bool
 1022 filter_menu(
 1023     t_function type,
 1024     struct t_group *group,
 1025     struct t_article *art)
 1026 {
 1027     const char *ptr_filter_from;
 1028     const char *ptr_filter_msgid;
 1029     const char *ptr_filter_subj;
 1030     const char *ptr_filter_help_scope;
 1031     const char *ptr_filter_quit_edit_save;
 1032     char *ptr;
 1033     char **list;
 1034     char comment_line[LEN];
 1035     char buf[LEN];
 1036     char keyedit[MAXKEYLEN], keyquit[MAXKEYLEN], keysave[MAXKEYLEN];
 1037     char text_time[PATH_LEN];
 1038     char double_time[PATH_LEN];
 1039     char quat_time[PATH_LEN];
 1040     int i, len, clen, flen;
 1041     struct t_filter_rule rule;
 1042     t_bool proceed;
 1043     t_bool ret;
 1044     t_function func, default_func = FILTER_SAVE;
 1045 
 1046     signal_context = cFilter;
 1047 
 1048     rule.comment = (struct t_filter_comment *) 0;
 1049     rule.text[0] = '\0';
 1050     rule.scope[0] = '\0';
 1051     rule.counter = 0;
 1052     rule.lines_cmp = FILTER_LINES_NO;
 1053     rule.lines_num = 0;
 1054     rule.from_ok = FALSE;
 1055     rule.lines_ok = FALSE;
 1056     rule.msgid_ok = FALSE;
 1057     rule.fullref = FILTER_MSGID;
 1058     rule.subj_ok = FALSE;
 1059     rule.icase = FALSE;
 1060     rule.score = 0;
 1061     rule.expire_time = FALSE;
 1062     rule.check_string = FALSE;
 1063 
 1064     comment_line[0] = '\0';
 1065 
 1066     /*
 1067      * setup correct text for user selected menu
 1068      */
 1069     printascii(keyedit, func_to_key(FILTER_EDIT, filter_keys));
 1070     printascii(keyquit, func_to_key(GLOBAL_QUIT, filter_keys));
 1071     printascii(keysave, func_to_key(FILTER_SAVE, filter_keys));
 1072 
 1073     if (type == GLOBAL_MENU_FILTER_KILL) {
 1074         ptr_filter_from = _(txt_kill_from);
 1075         ptr_filter_lines = _(txt_kill_lines);
 1076         ptr_filter_menu = _(txt_kill_menu);
 1077         ptr_filter_msgid = _(txt_kill_msgid);
 1078         ptr_filter_scope = _(txt_kill_scope);
 1079         ptr_filter_subj = _(txt_kill_subj);
 1080         ptr_filter_text = _(txt_kill_text);
 1081         ptr_filter_time = _(txt_kill_time);
 1082         ptr_filter_help_scope = _(txt_help_kill_scope);
 1083         ptr_filter_quit_edit_save = _(txt_quit_edit_save_kill);
 1084     } else {    /* type == GLOBAL_MENU_FILTER_SELECT */
 1085         ptr_filter_from = _(txt_select_from);
 1086         ptr_filter_lines = _(txt_select_lines);
 1087         ptr_filter_menu = _(txt_select_menu);
 1088         ptr_filter_msgid = _(txt_select_msgid);
 1089         ptr_filter_scope = _(txt_select_scope);
 1090         ptr_filter_subj = _(txt_select_subj);
 1091         ptr_filter_text = _(txt_select_text);
 1092         ptr_filter_time = _(txt_select_time);
 1093         ptr_filter_help_scope = _(txt_help_select_scope);
 1094         ptr_filter_quit_edit_save = _(txt_quit_edit_save_select);
 1095     }
 1096 
 1097     ptr_filter_comment = _(txt_filter_comment);
 1098     ptr_filter_groupname = group->name;
 1099 
 1100     clen = strwidth(_(txt_no));
 1101     clen = MAX(clen, strwidth(_(txt_yes)));
 1102     clen = MAX(clen, strwidth(_(txt_full)));
 1103     clen = MAX(clen, strwidth(_(txt_last)));
 1104     clen = MAX(clen, strwidth(_(txt_only)));
 1105 
 1106     flen = strwidth(ptr_filter_subj) - 2;
 1107     flen = MAX(flen, strwidth(ptr_filter_from) - 2);
 1108     flen = MAX(flen, strwidth(ptr_filter_msgid) - 2);
 1109 
 1110     len = cCOLS - flen - clen - 1 + 4;
 1111 
 1112     snprintf(text_time, sizeof(text_time), _(txt_time_default_days), tinrc.filter_days);
 1113     fmt_filter_menu_prompt(text_subj, sizeof(text_subj), ptr_filter_subj, len, art->subject);
 1114     snprintf(text_score, sizeof(text_score), _(txt_filter_score), (type == GLOBAL_MENU_FILTER_KILL ? -tinrc.score_kill : tinrc.score_select));
 1115     fmt_filter_menu_prompt(text_from, sizeof(text_from), ptr_filter_from, len, art->from);
 1116     fmt_filter_menu_prompt(text_msgid, sizeof(text_msgid), ptr_filter_msgid, len - 4, MSGID(art));
 1117 
 1118     print_filter_menu();
 1119 
 1120     /*
 1121      * None, one or multiple lines of comment.
 1122      * Continue until an empty line is entered.
 1123      * The empty line is ignored.
 1124      */
 1125     show_menu_help(_(txt_help_filter_comment));
 1126     while ((proceed = prompt_menu_string(INDEX_TOP, ptr_filter_comment, comment_line)) && comment_line[0] != '\0') {
 1127         rule.comment = add_filter_comment(rule.comment, comment_line);
 1128         comment_line[0] = '\0';
 1129     }
 1130     if (!proceed) {
 1131         rule.comment = free_filter_comment(rule.comment);
 1132         return FALSE;
 1133     }
 1134 
 1135     /*
 1136      * Text which might be used to filter on subj, from or msgid
 1137      */
 1138     show_menu_help(_(txt_help_filter_text));
 1139     if (!prompt_menu_string(INDEX_TOP + 2, ptr_filter_text, rule.text)) {
 1140         rule.comment = free_filter_comment(rule.comment);
 1141         return FALSE;
 1142     }
 1143 
 1144     if (*rule.text) {
 1145         list = my_malloc(sizeof(char *) * 8);
 1146         list[0] = (char *) _(txt_subj_line_only_case);
 1147         list[1] = (char *) _(txt_subj_line_only);
 1148         list[2] = (char *) _(txt_from_line_only_case);
 1149         list[3] = (char *) _(txt_from_line_only);
 1150         list[4] = (char *) _(txt_msgid_refs_line);
 1151         list[5] = (char *) _(txt_msgid_line_last);
 1152         list[6] = (char *) _(txt_msgid_line_only);
 1153         list[7] = (char *) _(txt_refs_line_only);
 1154 
 1155         i = get_choice(INDEX_TOP + 3, _(txt_help_filter_text_type), _(txt_filter_text_type), list, 8);
 1156         free(list);
 1157 
 1158         if (i == -1) {
 1159             rule.comment = free_filter_comment(rule.comment);
 1160             return FALSE;
 1161         }
 1162 
 1163         rule.counter = i;
 1164         switch (i) {
 1165             case FILTER_SUBJ_CASE_IGNORE:
 1166             case FILTER_FROM_CASE_IGNORE:
 1167                 rule.icase = TRUE;
 1168                 break;
 1169 
 1170             case FILTER_SUBJ_CASE_SENSITIVE:
 1171             case FILTER_FROM_CASE_SENSITIVE:
 1172             case FILTER_MSGID:
 1173             case FILTER_MSGID_LAST:
 1174             case FILTER_MSGID_ONLY:
 1175             case FILTER_REFS_ONLY:
 1176                 break;
 1177 
 1178             default: /* should not happen */
 1179                 /* CONSTANTCONDITION */
 1180                 assert(0 != 0);
 1181                 break;
 1182         }
 1183     }
 1184 
 1185     if (!*rule.text) {
 1186         rule.check_string = TRUE;
 1187         /*
 1188          * Subject:
 1189          */
 1190         list = my_malloc(sizeof(char *) * 2);
 1191         list[0] = (char *) _(txt_yes);
 1192         list[1] = (char *) _(txt_no);
 1193         i = get_choice(INDEX_TOP + 5, _(txt_help_filter_subj), text_subj, list, 2);
 1194         free(list);
 1195 
 1196         if (i == -1) {
 1197             rule.comment = free_filter_comment(rule.comment);
 1198             return FALSE;
 1199         } else
 1200             rule.subj_ok = (i == 0);
 1201 
 1202         /*
 1203          * From:
 1204          */
 1205         list = my_malloc(sizeof(char *) * 2);
 1206         if (rule.subj_ok) {
 1207             list[0] = (char *) _(txt_no);
 1208             list[1] = (char *) _(txt_yes);
 1209         } else {
 1210             list[0] = (char *) _(txt_yes);
 1211             list[1] = (char *) _(txt_no);
 1212         }
 1213         i = get_choice(INDEX_TOP + 6, _(txt_help_filter_from), text_from, list, 2);
 1214         free(list);
 1215 
 1216         if (i == -1) {
 1217             rule.comment = free_filter_comment(rule.comment);
 1218             return FALSE;
 1219         } else
 1220             rule.from_ok = rule.subj_ok ? (i != 0) : (i == 0);
 1221 
 1222         /*
 1223          * Message-ID:
 1224          */
 1225         list = my_malloc(sizeof(char *) * 4);
 1226         if (rule.subj_ok || rule.from_ok) {
 1227             list[0] = (char *) _(txt_no);
 1228             list[1] = (char *) _(txt_full);
 1229             list[2] = (char *) _(txt_last);
 1230             list[3] = (char *) _(txt_only);
 1231         } else {
 1232             list[0] = (char *) _(txt_full);
 1233             list[1] = (char *) _(txt_last);
 1234             list[2] = (char *) _(txt_only);
 1235             list[3] = (char *) _(txt_no);
 1236         }
 1237         i = get_choice(INDEX_TOP + 7, _(txt_help_filter_msgid), text_msgid, list, 4);
 1238         free(list);
 1239 
 1240         if (i == -1) {
 1241             rule.comment = free_filter_comment(rule.comment);
 1242             return FALSE;
 1243         } else {
 1244             switch ((rule.subj_ok || rule.from_ok) ? i : i + 1) {
 1245                 case 0:
 1246                 case 4:
 1247                     rule.msgid_ok = FALSE;
 1248                     rule.fullref = FILTER_MSGID;
 1249                     break;
 1250 
 1251                 case 1:
 1252                     rule.msgid_ok = TRUE;
 1253                     rule.fullref = FILTER_MSGID;
 1254                     break;
 1255 
 1256                 case 2:
 1257                     rule.msgid_ok = TRUE;
 1258                     rule.fullref = FILTER_MSGID_LAST;
 1259                     break;
 1260 
 1261                 case 3:
 1262                     rule.msgid_ok = TRUE;
 1263                     rule.fullref = FILTER_MSGID_ONLY;
 1264                     break;
 1265 
 1266                 default: /* should not happen */
 1267                     /* CONSTANTCONDITION */
 1268                     assert(0 != 0);
 1269                     break;
 1270             }
 1271         }
 1272 
 1273     }
 1274 
 1275     /*
 1276      * Lines:
 1277      */
 1278     show_menu_help(_(txt_help_filter_lines));
 1279 
 1280     buf[0] = '\0';
 1281 
 1282     if (!prompt_menu_string(INDEX_TOP + 9, ptr_filter_lines, buf)) {
 1283         rule.comment = free_filter_comment(rule.comment);
 1284         return FALSE;
 1285     }
 1286 
 1287     /*
 1288      * Get the < > sign if any for the lines rule
 1289      */
 1290     ptr = buf;
 1291     while (*ptr == ' ')
 1292         ptr++;
 1293 
 1294     if (*ptr == '>') {
 1295         rule.lines_cmp = FILTER_LINES_GT;
 1296         ptr++;
 1297     } else if (*ptr == '<') {
 1298         rule.lines_cmp = FILTER_LINES_LT;
 1299         ptr++;
 1300     } else if (*ptr == '=') {
 1301         rule.lines_cmp = FILTER_LINES_EQ;
 1302         ptr++;
 1303     }
 1304 
 1305     if (*ptr)
 1306         rule.lines_num = abs(atoi(ptr));
 1307 
 1308     if (rule.lines_num && rule.lines_cmp == FILTER_LINES_NO)
 1309         rule.lines_cmp = FILTER_LINES_EQ;
 1310 
 1311     if (rule.lines_cmp != FILTER_LINES_NO && rule.lines_num)
 1312         rule.lines_ok = TRUE;
 1313 
 1314     /*
 1315      * Scoring value
 1316      */
 1317     snprintf(buf, sizeof(buf), _(txt_filter_score_help), SCORE_MAX);
 1318     show_menu_help(buf);
 1319 
 1320     buf[0] = '\0';
 1321     if (!prompt_menu_string(INDEX_TOP + 10, text_score, buf)) {
 1322         rule.comment = free_filter_comment(rule.comment);
 1323         return FALSE;
 1324     }
 1325 
 1326     /* check if a score has been entered */
 1327     if (buf[0] != '\0')
 1328         /* use entered score */
 1329         rule.score = atoi(buf);
 1330     else {
 1331         /* use default score */
 1332         if (type == GLOBAL_MENU_FILTER_KILL)
 1333             rule.score = tinrc.score_kill;
 1334         else /* type == GLOBAL_MENU_FILTER_SELECT */
 1335             rule.score = tinrc.score_select;
 1336     }
 1337 
 1338     if (!rule.score) { /* ignore 0 scores */
 1339         rule.comment = free_filter_comment(rule.comment);
 1340         return FALSE;
 1341     }
 1342 
 1343     /*
 1344      * assure we are in range
 1345      */
 1346     if (rule.score < 0)
 1347         rule.score = abs(rule.score);
 1348     if (rule.score > SCORE_MAX)
 1349         rule.score = SCORE_MAX;
 1350 
 1351     /* get the right sign for the score */
 1352     if (type == GLOBAL_MENU_FILTER_KILL)
 1353         rule.score = -rule.score;
 1354 
 1355     /*
 1356      * Expire time
 1357      */
 1358     snprintf(double_time, sizeof(double_time), "2x %s", text_time);
 1359     snprintf(quat_time, sizeof(quat_time), "4x %s", text_time);
 1360     list = my_malloc(sizeof(char *) * 4);
 1361     list[0] = (char *) _(txt_unlimited_time);
 1362     list[1] = text_time;
 1363     list[2] = double_time;
 1364     list[3] = quat_time;
 1365     i = get_choice(INDEX_TOP + 11, _(txt_help_filter_time), ptr_filter_time, list, 4);
 1366     free(list);
 1367 
 1368     if (i == -1) {
 1369         rule.comment = free_filter_comment(rule.comment);
 1370         return FALSE;
 1371     }
 1372 
 1373     rule.expire_time = i;
 1374 
 1375     /*
 1376      * Scope
 1377      */
 1378     if (*rule.text || rule.subj_ok || rule.from_ok || rule.msgid_ok || rule.lines_ok) {
 1379         int j = 0;
 1380 
 1381         list = my_malloc(sizeof(char *) * 2); /* at least 2 scopes */
 1382         list[j++] = my_strdup(group->name);
 1383         list[j] = my_strdup(list[j - 1]);
 1384         while ((ptr = strrchr(list[j], '.')) != NULL) {
 1385             *(++ptr) = '*';
 1386             *(++ptr) = '\0';
 1387             j++;
 1388             list = my_realloc(list, sizeof(char *) * (j + 1)); /* one element more */
 1389             list[j] = my_strdup(list[j - 1]);
 1390             list[j][strlen(list[j]) - 2] = '\0';
 1391         }
 1392         free(list[j]); /* this copy isn't needed anymore */
 1393         list[j] = (char *) _(txt_all_groups);
 1394 
 1395         if ((i = get_choice(INDEX_TOP + 13, ptr_filter_help_scope, ptr_filter_scope, list, j + 1)) > 0)
 1396             my_strncpy(rule.scope, i == j ? "*" : list[i], sizeof(rule.scope) - 1);
 1397 
 1398         for (j--; j >= 0; j--)
 1399             free(list[j]);
 1400         free(list);
 1401 
 1402         if (i == -1) {
 1403             rule.comment = free_filter_comment(rule.comment);
 1404             return FALSE;
 1405         }
 1406     } else {
 1407         rule.comment = free_filter_comment(rule.comment);
 1408         return FALSE;
 1409     }
 1410 
 1411     forever {
 1412         func = prompt_slk_response(default_func, filter_keys,
 1413                 ptr_filter_quit_edit_save, keyquit, keyedit, keysave);
 1414         switch (func) {
 1415 
 1416         case FILTER_EDIT:
 1417             add_filter_rule(group, art, &rule, FALSE); /* save the rule */
 1418             rule.comment = free_filter_comment(rule.comment);
 1419             if (!invoke_editor(filter_file, filter_file_offset, NULL))
 1420                 return FALSE;
 1421             unfilter_articles(group);
 1422             (void) read_filter_file(filter_file);
 1423             return TRUE;
 1424             /* keep lint quiet: */
 1425             /* FALLTHROUGH */
 1426 
 1427         case GLOBAL_QUIT:
 1428         case GLOBAL_ABORT:
 1429             rule.comment = free_filter_comment(rule.comment);
 1430             return FALSE;
 1431             /* keep lint quiet: */
 1432             /* FALLTHROUGH */
 1433 
 1434         case FILTER_SAVE:
 1435             /*
 1436              * Add the filter rule and save it to the filter file
 1437              */
 1438             ret = add_filter_rule(group, art, &rule, FALSE);
 1439             rule.comment = free_filter_comment(rule.comment);
 1440             return ret;
 1441             /* keep lint quiet: */
 1442             /* FALLTHROUGH */
 1443 
 1444         default:
 1445             break;
 1446         }
 1447     }
 1448     /* NOTREACHED */
 1449     return FALSE;
 1450 }
 1451 
 1452 
 1453 /*
 1454  * Quick command to add an auto-select / kill filter to specified groups filter
 1455  */
 1456 t_bool
 1457 quick_filter(
 1458     t_function type,
 1459     struct t_group *group,
 1460     struct t_article *art)
 1461 {
 1462     char *scope;
 1463     char txt[LEN];
 1464     int header, expire, icase;
 1465     struct t_filter_rule rule;
 1466     t_bool ret;
 1467 
 1468     if (type == GLOBAL_QUICK_FILTER_KILL) {
 1469         header = group->attribute->quick_kill_header;
 1470         expire = group->attribute->quick_kill_expire;
 1471         /* ON=case sensitive, OFF=ignore case -> invert */
 1472         icase = bool_not(group->attribute->quick_kill_case);
 1473         scope = group->attribute->quick_kill_scope;
 1474     } else {    /* type == GLOBAL_QUICK_FILTER_SELECT */
 1475         header = group->attribute->quick_select_header;
 1476         expire = group->attribute->quick_select_expire;
 1477         /* ON=case sensitive, OFF=ignore case -> invert */
 1478         icase = bool_not(group->attribute->quick_select_case);
 1479         scope = group->attribute->quick_select_scope;
 1480     }
 1481 
 1482 #ifdef DEBUG
 1483     if (debug & DEBUG_FILTER)
 1484         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);
 1485 #endif /* DEBUG */
 1486 
 1487     /*
 1488      * Setup rules
 1489      */
 1490     if (strlen(BlankIfNull(scope)) > (sizeof(rule.scope) - 1))
 1491         return FALSE;
 1492     my_strncpy(rule.scope, BlankIfNull(scope), sizeof(rule.scope) - 1);
 1493     rule.counter = 0;
 1494     rule.lines_cmp = FILTER_LINES_NO;
 1495     rule.lines_num = 0;
 1496     rule.lines_ok = (header == FILTER_LINES);
 1497     rule.msgid_ok = (header == FILTER_MSGID) || (header == FILTER_MSGID_LAST);
 1498     rule.fullref = header; /* value is directly used to select correct filter type */
 1499     rule.from_ok = (header == FILTER_FROM_CASE_SENSITIVE || header == FILTER_FROM_CASE_IGNORE);
 1500     rule.subj_ok = (header == FILTER_SUBJ_CASE_SENSITIVE || header == FILTER_SUBJ_CASE_IGNORE);
 1501 
 1502     /* create an auto-comment. */
 1503     if (type == GLOBAL_QUICK_FILTER_KILL)
 1504         snprintf(txt, sizeof(txt), "%s%s%c%s%s%s", _(txt_filter_rule_created), "'", ']', "' (", _(txt_help_article_quick_kill), ").");
 1505     else
 1506         snprintf(txt, sizeof(txt), "%s%s%c%s%s%s", _(txt_filter_rule_created), "'", '[', "' (", _(txt_help_article_quick_select), ").");
 1507     rule.comment = add_filter_comment(NULL, txt);
 1508 
 1509     rule.text[0] = '\0';
 1510     rule.icase = icase;
 1511     rule.expire_time = expire;
 1512     rule.check_string = TRUE;
 1513     rule.score = (type == GLOBAL_QUICK_FILTER_KILL) ? tinrc.score_kill : tinrc.score_select;
 1514 
 1515     ret = add_filter_rule(group, art, &rule, TRUE);
 1516     rule.comment = free_filter_comment(rule.comment);
 1517     return ret;
 1518 }
 1519 
 1520 
 1521 /*
 1522  * Quick command to add an auto-select filter to the article that user
 1523  * has just posted. Selects on Subject: line with limited expire time.
 1524  * Don't process if GROUP_TYPE_MAIL || GROUP_TYPE_SAVE
 1525  */
 1526 t_bool
 1527 quick_filter_select_posted_art(
 1528     struct t_group *group,
 1529     const char *subj,
 1530     const char *a_message_id)   /* return value is always ignored */
 1531 {
 1532     t_bool filtered = FALSE;
 1533     char txt[LEN];
 1534 
 1535     if (group->type == GROUP_TYPE_NEWS) {
 1536         struct t_article art;
 1537         struct t_filter_rule rule;
 1538 
 1539 #ifdef __cplusplus /* keep C++ quiet */
 1540         rule.scope[0] = '\0';
 1541 #endif /* __cplusplus */
 1542 
 1543         if (strlen(group->name) > (sizeof(rule.scope) - 1)) /* groupname to long? */
 1544             return FALSE;
 1545 
 1546         /*
 1547          * Setup rules
 1548          */
 1549         rule.counter = 0;
 1550         rule.lines_cmp = FILTER_LINES_NO;
 1551         rule.lines_num = 0;
 1552         rule.from_ok = FALSE;
 1553         rule.lines_ok = FALSE;
 1554         rule.msgid_ok = FALSE;
 1555         rule.fullref = FILTER_MSGID;
 1556         rule.subj_ok = TRUE;
 1557         rule.text[0] = '\0';
 1558         rule.icase = FALSE;
 1559         rule.expire_time = TRUE;
 1560         rule.check_string = TRUE;
 1561         rule.score = tinrc.score_select;
 1562 
 1563         strcpy(rule.scope, group->name);
 1564 
 1565         /* create an auto-comment. */
 1566         snprintf(txt, sizeof(txt), "%s%s", _(txt_filter_rule_created), "add_posted_to_filter=ON.");
 1567         rule.comment = add_filter_comment(NULL, txt);
 1568 
 1569         /*
 1570          * Setup dummy article with posted articles subject
 1571          * xor Message-ID
 1572          */
 1573         set_article(&art);
 1574         if (*a_message_id) {
 1575             /* initialize art->refptr */
 1576             struct {
 1577                 struct t_msgid *next;
 1578                 struct t_msgid *parent;
 1579                 struct t_msgid *sibling;
 1580                 struct t_msgid *child;
 1581                 int article;
 1582                 char txt[HEADER_LEN];
 1583             } refptr_dummyart;
 1584 
 1585             rule.subj_ok = FALSE;
 1586             rule.msgid_ok = TRUE;
 1587             refptr_dummyart.next = (struct t_msgid *) 0;
 1588             refptr_dummyart.parent = (struct t_msgid *) 0;
 1589             refptr_dummyart.sibling = (struct t_msgid *) 0;
 1590             refptr_dummyart.child = (struct t_msgid *) 0;
 1591             refptr_dummyart.article = ART_NORMAL;
 1592             my_strncpy(refptr_dummyart.txt, a_message_id, HEADER_LEN);
 1593             /* Hack */
 1594             art.refptr = (struct t_msgid *) &refptr_dummyart;
 1595 
 1596             filtered = add_filter_rule(group, &art, &rule, FALSE);
 1597         } else {
 1598             art.subject = my_strdup(subj);
 1599             filtered = add_filter_rule(group, &art, &rule, FALSE);
 1600             FreeIfNeeded(art.subject);
 1601         }
 1602         rule.comment = free_filter_comment(rule.comment);
 1603     }
 1604     return filtered;
 1605 }
 1606 
 1607 
 1608 /*
 1609  * API to add filter rule to the local or global filter array
 1610  */
 1611 static t_bool
 1612 add_filter_rule(
 1613     struct t_group *group,
 1614     struct t_article *art,
 1615     struct t_filter_rule *rule,
 1616     t_bool quick_filter_rule)
 1617 {
 1618     char acbuf[PATH_LEN];
 1619     char sbuf[(sizeof(acbuf) / 2)]; /* half as big as acbuf so quote_wild(sbuf) fits into acbuf */
 1620     int i = glob_filter.num;
 1621     t_bool filtered = FALSE;
 1622     time_t current_time;
 1623     struct t_filter *ptr;
 1624 
 1625     if (glob_filter.num >= glob_filter.max)
 1626         expand_filter_array(&glob_filter);
 1627 
 1628     ptr = glob_filter.filter;
 1629 
 1630     ptr[i].inscope = TRUE;
 1631     ptr[i].icase = FALSE;
 1632     ptr[i].fullref = FILTER_MSGID;
 1633     ptr[i].comment = (struct t_filter_comment *) 0;
 1634     ptr[i].scope = NULL;
 1635     ptr[i].subj = NULL;
 1636     ptr[i].from = NULL;
 1637     ptr[i].msgid = NULL;
 1638     ptr[i].lines_cmp = rule->lines_cmp;
 1639     ptr[i].lines_num = rule->lines_num;
 1640     ptr[i].gnksa_cmp = FILTER_LINES_NO;
 1641     ptr[i].gnksa_num = 0;
 1642     ptr[i].score = rule->score;
 1643     ptr[i].xref = NULL;
 1644 
 1645     if (rule->comment != NULL)
 1646         ptr[i].comment = copy_filter_comment(rule->comment, ptr[i].comment);
 1647 
 1648     if (rule->scope[0] == '\0') /* replace empty scope with current group name */
 1649         ptr[i].scope = my_strdup(group->name);
 1650     else {
 1651         if ((rule->scope[0] != '*') && (rule->scope[1] != '\0')) /* copy non-global scope */
 1652             ptr[i].scope = my_strdup(rule->scope);
 1653     }
 1654 
 1655     (void) time(&current_time);
 1656     switch (rule->expire_time) {
 1657         case 1:
 1658             ptr[i].time = current_time + (time_t) (tinrc.filter_days * DAY);
 1659             break;
 1660 
 1661         case 2:
 1662             ptr[i].time = current_time + (time_t) (tinrc.filter_days * DAY * 2);
 1663             break;
 1664 
 1665         case 3:
 1666             ptr[i].time = current_time + (time_t) (tinrc.filter_days * DAY * 4);
 1667             break;
 1668 
 1669         default:
 1670             ptr[i].time = (time_t) 0;
 1671             break;
 1672     }
 1673 
 1674     ptr[i].icase = rule->icase;
 1675     if (*rule->text) {
 1676         snprintf(acbuf, sizeof(acbuf), REGEX_FMT, quote_wild_whitespace(rule->text));
 1677 
 1678         switch (rule->counter) {
 1679             case FILTER_SUBJ_CASE_IGNORE:
 1680             case FILTER_SUBJ_CASE_SENSITIVE:
 1681                 ptr[i].subj = my_strdup(acbuf);
 1682                 break;
 1683 
 1684             case FILTER_FROM_CASE_IGNORE:
 1685             case FILTER_FROM_CASE_SENSITIVE:
 1686                 ptr[i].from = my_strdup(acbuf);
 1687                 break;
 1688 
 1689             case FILTER_MSGID:
 1690             case FILTER_MSGID_LAST:
 1691             case FILTER_MSGID_ONLY:
 1692             case FILTER_REFS_ONLY:
 1693                 ptr[i].msgid = my_strdup(acbuf);
 1694                 ptr[i].fullref = rule->counter;
 1695                 break;
 1696 
 1697             default: /* should not happen */
 1698                 /* CONSTANTCONDITION */
 1699                 assert(0 != 0);
 1700                 break;
 1701         }
 1702         filtered = TRUE;
 1703         glob_filter.num++;
 1704     } else {
 1705         /*
 1706          * STRCPY() truncates subject/from/message-id so it fits
 1707          * into acbuf even after quote_wild()
 1708          */
 1709         if (rule->subj_ok) {
 1710             STRCPY(sbuf, art->subject);
 1711             snprintf(acbuf, sizeof(acbuf), REGEX_FMT, (rule->check_string ? quote_wild(sbuf) : sbuf));
 1712             ptr[i].subj = my_strdup(acbuf);
 1713         }
 1714         if (rule->from_ok) {
 1715             STRCPY(sbuf, art->from);
 1716             snprintf(acbuf, sizeof(acbuf), REGEX_FMT, quote_wild(sbuf));
 1717             ptr[i].from = my_strdup(acbuf);
 1718         }
 1719         /*
 1720          * message-ids should be quoted
 1721          */
 1722         if (rule->msgid_ok) {
 1723             /*
 1724              * If threading by references is set, and a quick kill is applied
 1725              * (in group level), it is applied with the data of the root
 1726              * article of the thread built by tin.
 1727              * In case of threading by references, if tin's root is not the
 1728              * *real* root of thread (which is the first entry in references
 1729              * field) any applying of filtering for MSGID (or MSGID_LAST)
 1730              * doesn't work, because the filter is applied with the data of
 1731              * tin's root article which doesn't cover the other articles in
 1732              * the thread.
 1733              * So the thread remains open (in group level). To overcome this,
 1734              * the first msgid from references field is taken in this case.
 1735              */
 1736             if (quick_filter_rule && group->attribute->thread_articles == THREAD_REFS &&
 1737                 (group->attribute->quick_kill_header == FILTER_MSGID ||
 1738                  group->attribute->quick_kill_header == FILTER_REFS_ONLY) &&
 1739                  art->refptr->parent != NULL)
 1740             {
 1741                 /* not real parent, take first references entry as MID */
 1742                 struct t_msgid *xptr;
 1743 
 1744                 for (xptr = art->refptr->parent; xptr->parent != NULL; xptr = xptr->parent)
 1745                     ;
 1746                 STRCPY(sbuf, xptr->txt);
 1747             } else {
 1748                 STRCPY(sbuf, MSGID(art));
 1749             }
 1750             snprintf(acbuf, sizeof(acbuf), REGEX_FMT, quote_wild(sbuf));
 1751             ptr[i].msgid = my_strdup(acbuf);
 1752             ptr[i].fullref = rule->fullref;
 1753         }
 1754         if (rule->subj_ok || rule->from_ok || rule->msgid_ok || rule->lines_ok) {
 1755             filtered = TRUE;
 1756             glob_filter.num++;
 1757         }
 1758     }
 1759 
 1760     if (filtered) {
 1761 #ifdef DEBUG
 1762         if (debug & DEBUG_FILTER)
 1763             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);
 1764 #endif /* DEBUG */
 1765         write_filter_file(filter_file);
 1766     }
 1767     return filtered;
 1768 }
 1769 
 1770 
 1771 /*
 1772  * We assume that any articles which are tagged as killed are also
 1773  * tagged as being read BECAUSE they were killed. We retag them as
 1774  * being unread if they were unread before killing (ART_KILLED_UNREAD).
 1775  * Selected articles will be un"select"ed.
 1776  */
 1777 void
 1778 unfilter_articles(
 1779     struct t_group *group)
 1780 {
 1781     int i;
 1782 
 1783     for_each_art(i) {
 1784         arts[i].score = 0;
 1785         if (IS_KILLED(i)) {
 1786             if (IS_KILLED_UNREAD(i))
 1787                 art_mark(group, &arts[i], ART_UNREAD);
 1788             arts[i].killed = ART_NOTKILLED;
 1789         }
 1790         if (IS_SELECTED(i))
 1791             arts[i].selected = FALSE;
 1792     }
 1793 }
 1794 
 1795 
 1796 /*
 1797  * Filter any articles in specified group.
 1798  * Apply global filter rules followed by group filter rules.
 1799  * In global rules check if scope field set to determine if
 1800  * filter applys to current group.
 1801  */
 1802 t_bool
 1803 filter_articles(
 1804     struct t_group *group)
 1805 {
 1806     char buf[LEN];
 1807     int num, inscope;
 1808     int i, j;
 1809     struct t_filter *ptr;
 1810     struct regex_cache *regex_cache_subj = NULL;
 1811     struct regex_cache *regex_cache_from = NULL;
 1812     struct regex_cache *regex_cache_msgid = NULL;
 1813     struct regex_cache *regex_cache_xref = NULL;
 1814     t_bool filtered = FALSE;
 1815     t_bool error = FALSE;
 1816 
 1817     /*
 1818      * check if there are any global filter rules
 1819      */
 1820     if (group->glob_filter->num == 0)
 1821         return filtered;
 1822 
 1823     /*
 1824      * Apply global filter rules first if there are any entries
 1825      */
 1826     /*
 1827      * Check if any scope rules are active for this group
 1828      * ie. group=comp.os.linux.help  scope=comp.os.linux.*
 1829      */
 1830     inscope = set_filter_scope(group);
 1831     if (!cmd_line && !batch_mode)
 1832         wait_message(0, _(txt_filter_global_rules), inscope, group->glob_filter->num);
 1833     num = group->glob_filter->num;
 1834     ptr = group->glob_filter->filter;
 1835 
 1836     /*
 1837      * set up cache tables for all types of filter rules
 1838      * (only for regexp matching)
 1839      */
 1840     if (tinrc.wildcard) {
 1841         size_t msiz;
 1842 
 1843         msiz = sizeof(struct regex_cache) * num;
 1844         regex_cache_subj = my_malloc(msiz);
 1845         regex_cache_from = my_malloc(msiz);
 1846         regex_cache_msgid = my_malloc(msiz);
 1847         regex_cache_xref = my_malloc(msiz);
 1848         for (j = 0; j < num; j++) {
 1849             regex_cache_subj[j].re = NULL;
 1850             regex_cache_subj[j].extra = NULL;
 1851             regex_cache_from[j].re = NULL;
 1852             regex_cache_from[j].extra = NULL;
 1853             regex_cache_msgid[j].re = NULL;
 1854             regex_cache_msgid[j].extra = NULL;
 1855             regex_cache_xref[j].re = NULL;
 1856             regex_cache_xref[j].extra = NULL;
 1857         }
 1858     }
 1859 
 1860     /*
 1861      * loop through all arts applying global & local filtering rules
 1862      *
 1863      * TODO: allow iKeyAbort to stop filtering
 1864      */
 1865     for (i = 0; (i < top_art) && !error; i++) {
 1866         arts[i].score = 0;
 1867 
 1868         if (tinrc.kill_level == KILL_UNREAD && IS_READ(i)) /* skip only when the article is read */
 1869             continue;
 1870 
 1871         for (j = 0; j < num && !error; j++) {
 1872             if (ptr[j].inscope) {
 1873                 /*
 1874                  * Filter on Subject: line
 1875                  */
 1876                 if (ptr[j].subj != NULL) {
 1877                     switch (test_regex(arts[i].subject, ptr[j].subj, ptr[j].icase, &regex_cache_subj[j])) {
 1878                         case 1:
 1879                             SET_FILTER(group, i, j);
 1880                             break;
 1881 
 1882                         case -1:
 1883                             error = TRUE;
 1884                             break;
 1885 
 1886                         default:
 1887                             break;
 1888                     }
 1889                 }
 1890 
 1891                 /*
 1892                  * Filter on From: line
 1893                  */
 1894                 if (ptr[j].from != NULL) {
 1895                     if (arts[i].name != NULL)
 1896                         snprintf(buf, sizeof(buf), "%s (%s)", arts[i].from, arts[i].name);
 1897                     else
 1898                         STRCPY(buf, arts[i].from);
 1899 
 1900                     switch (test_regex(buf, ptr[j].from, ptr[j].icase, &regex_cache_from[j])) {
 1901                         case 1:
 1902                             SET_FILTER(group, i, j);
 1903                             break;
 1904 
 1905                         case -1:
 1906                             error = TRUE;
 1907                             break;
 1908 
 1909                         default:
 1910                             break;
 1911                     }
 1912                 }
 1913 
 1914                 /*
 1915                  * Filter on Message-ID: line
 1916                  * Apply to Message-ID: & References: lines or
 1917                  * Message-ID: & last entry from References: line
 1918                  * Case is important here
 1919                  */
 1920                 if (ptr[j].msgid != NULL) {
 1921                     char *refs = NULL;
 1922                     const char *myrefs = NULL;
 1923                     const char *mymsgid = NULL;
 1924                     int x;
 1925                     struct t_article *art = &arts[i];
 1926                     /*
 1927                      * TODO: nice idea del'd; better apply one rule on all
 1928                      *       fitting articles, so we can switch to an appropriate
 1929                      *       algorithm for each kind of rule, including the
 1930                      *       deleted one.
 1931                      */
 1932 
 1933                     /* myrefs does not need to be freed */
 1934 
 1935                     /* use full references header or just the last entry? */
 1936                     switch (ptr[j].fullref) {
 1937                         case FILTER_MSGID:
 1938                             myrefs = REFS(art, refs);
 1939                             mymsgid = MSGID(art);
 1940                             break;
 1941 
 1942                         case FILTER_MSGID_LAST:
 1943                             myrefs = art->refptr ? (art->refptr->parent ? art->refptr->parent->txt : "") : "";
 1944                             mymsgid = MSGID(art);
 1945                             break;
 1946 
 1947                         case FILTER_MSGID_ONLY:
 1948                             myrefs = "";
 1949                             mymsgid = MSGID(art);
 1950                             break;
 1951 
 1952                         case FILTER_REFS_ONLY:
 1953                             myrefs = REFS(art, refs);
 1954                             mymsgid = "";
 1955                             break;
 1956 
 1957                         default: /* should not happen */
 1958                             /* CONSTANTCONDITION */
 1959                             assert(0 != 0);
 1960                             break;
 1961                     }
 1962 
 1963                     if ((x = test_regex(myrefs, ptr[j].msgid, FALSE, &regex_cache_msgid[j])) == 0) /* no match */
 1964                         x = test_regex(mymsgid, ptr[j].msgid, FALSE, &regex_cache_msgid[j]);
 1965 
 1966                     switch (x) {
 1967                         case 1:
 1968                             SET_FILTER(group, i, j);
 1969 #ifdef DEBUG
 1970                             if (debug & DEBUG_FILTER)
 1971                                 debug_print_file("FILTER", "Group[%s] Rule[%d][%s] ArtMSGID[%s] Artnum[%d]", group->name, j, ptr[j].msgid, mymsgid, i);
 1972 #endif /* DEBUG */
 1973                             break;
 1974 
 1975                         case -1:
 1976                             error = TRUE;
 1977                             break;
 1978 
 1979                         default:
 1980                             break;
 1981                     }
 1982                     FreeIfNeeded(refs);
 1983                 }
 1984                 /*
 1985                  * Filter on Lines: line
 1986                  */
 1987                 if ((ptr[j].lines_cmp != FILTER_LINES_NO) && (arts[i].line_count >= 0)) {
 1988                     switch (ptr[j].lines_cmp) {
 1989                         case FILTER_LINES_EQ:
 1990                             if (arts[i].line_count == ptr[j].lines_num) {
 1991                                 SET_FILTER(group, i, j);
 1992                             }
 1993                             break;
 1994 
 1995                         case FILTER_LINES_LT:
 1996                             if (arts[i].line_count < ptr[j].lines_num) {
 1997                                 SET_FILTER(group, i, j);
 1998                             }
 1999                             break;
 2000 
 2001                         case FILTER_LINES_GT:
 2002                             if (arts[i].line_count > ptr[j].lines_num) {
 2003                                 SET_FILTER(group, i, j);
 2004                             }
 2005                             break;
 2006 
 2007                         default:
 2008                             break;
 2009                     }
 2010                 }
 2011 
 2012                 /*
 2013                  * Filter on GNKSA code
 2014                  */
 2015                 if ((ptr[j].gnksa_cmp != FILTER_LINES_NO) && (arts[i].gnksa_code >= 0)) {
 2016                     switch (ptr[j].gnksa_cmp) {
 2017                         case FILTER_LINES_EQ:
 2018                             if (arts[i].gnksa_code == ptr[j].gnksa_num) {
 2019                                 SET_FILTER(group, i, j);
 2020                             }
 2021                             break;
 2022 
 2023                         case FILTER_LINES_LT:
 2024                             if (arts[i].gnksa_code < ptr[j].gnksa_num) {
 2025                                 SET_FILTER(group, i, j);
 2026                             }
 2027                             break;
 2028 
 2029                         case FILTER_LINES_GT:
 2030                             if (arts[i].gnksa_code > ptr[j].gnksa_num) {
 2031                                 SET_FILTER(group, i, j);
 2032                             }
 2033                             break;
 2034 
 2035                         default:
 2036                             break;
 2037                     }
 2038                 }
 2039 
 2040                 /*
 2041                  * Filter on Xref: lines
 2042                  *
 2043                  * "news.foo.bar foo.bar:666 bar.bar:999"
 2044                  * is turned into the Newsgroups like string
 2045                  * foo.bar,bar.bar
 2046                  */
 2047                 if (arts[i].xref && *arts[i].xref) {
 2048                     if (ptr[j].score && ptr[j].xref != NULL) {
 2049                         char *s, *e, *k;
 2050                         t_bool skip = FALSE;
 2051 
 2052                         s = arts[i].xref;
 2053                         if (strchr(s, ' ') || strchr(s, '\t')) {
 2054                             while (*s && !isspace((int) *s))    /* skip server name */
 2055                                 s++;
 2056                             while (*s && isspace((int) *s))
 2057                                 s++;
 2058                         }
 2059 #ifdef DEBUG
 2060                         else { /* server name missing in overview, i.e. colobus 2.1 */
 2061                             if (debug & DEBUG_FILTER) { /* TODO: lang.c, _()? */
 2062                                 debug_print_file("FILTER", "Malformed overview entry: servername missing.");
 2063                                 debug_print_file("FILTER", "\t Xref: %s", arts[i].xref);
 2064                             }
 2065                         }
 2066 #endif /* DEBUG */
 2067                         if (strlen(s)) {
 2068                             /* reformat */
 2069                             k = e = my_malloc(strlen(s) + 1);
 2070                             while (*s) {
 2071                                 if (*s == ':') {
 2072                                     *e++ = ',';
 2073                                     skip = TRUE;
 2074                                 }
 2075                                 if (*s != ':' && !isspace((int) *s) && !skip)
 2076                                     *e++ = *s;
 2077                                 if (isspace((int) *s))
 2078                                     skip = FALSE;
 2079                                 s++;
 2080                             }
 2081                             *--e = '\0';
 2082                         } else {
 2083 #ifdef DEBUG
 2084                             if (debug & DEBUG_FILTER) /* TODO: lang.c, _()? */
 2085                                 debug_print_file("FILTER", "Skipping xref filter");
 2086 #endif /* DEBUG */
 2087                             error = TRUE;
 2088                             break;
 2089                         }
 2090 
 2091                         if (ptr[j].xref != NULL) {
 2092                             switch (test_regex(k, ptr[j].xref, ptr[j].icase, &regex_cache_xref[j])) {
 2093                                 case 1:
 2094                                     SET_FILTER(group, i, j);
 2095                                     break;
 2096 
 2097                                 case -1:
 2098                                     error = TRUE;
 2099                                     break;
 2100 
 2101                                 default:
 2102                                     break;
 2103                             }
 2104                         }
 2105                         free(k);
 2106                     }
 2107                 }
 2108             }
 2109         }
 2110     }
 2111 
 2112     /*
 2113      * throw away the contents of all regex_caches
 2114      */
 2115     if (tinrc.wildcard) {
 2116         for (j = 0; j < num; j++) {
 2117             FreeIfNeeded(regex_cache_subj[j].re);
 2118             FreeIfNeeded(regex_cache_subj[j].extra);
 2119             FreeIfNeeded(regex_cache_from[j].re);
 2120             FreeIfNeeded(regex_cache_from[j].extra);
 2121             FreeIfNeeded(regex_cache_msgid[j].re);
 2122             FreeIfNeeded(regex_cache_msgid[j].extra);
 2123             FreeIfNeeded(regex_cache_xref[j].re);
 2124             FreeIfNeeded(regex_cache_xref[j].extra);
 2125         }
 2126         free(regex_cache_subj);
 2127         free(regex_cache_from);
 2128         free(regex_cache_msgid);
 2129         free(regex_cache_xref);
 2130     }
 2131 
 2132     /*
 2133      * now entering the main filter loop:
 2134      * all articles have scored, so do kill & select
 2135      */
 2136     if (!error) {
 2137         for_each_art(i) {
 2138             if (arts[i].score <= tinrc.score_limit_kill) {
 2139                 if (arts[i].status == ART_UNREAD)
 2140                     arts[i].killed = ART_KILLED_UNREAD;
 2141                 else
 2142                     arts[i].killed = ART_KILLED;
 2143                 filtered = TRUE;
 2144                 art_mark(group, &arts[i], ART_READ);
 2145                 if (group->attribute->show_only_unread_arts)
 2146                     arts[i].keep_in_base = FALSE;
 2147             } else if (arts[i].score >= tinrc.score_limit_select) {
 2148                 arts[i].selected = TRUE;
 2149             }
 2150         }
 2151     }
 2152     if (!cmd_line && !batch_mode)
 2153         clear_message();
 2154 
 2155     return filtered;
 2156 }
 2157 
 2158 
 2159 static int
 2160 set_filter_scope(
 2161     struct t_group *group)
 2162 {
 2163     int i, num, inscope;
 2164     struct t_filter *ptr, *prev;
 2165 
 2166     inscope = num = group->glob_filter->num;
 2167     prev = ptr = group->glob_filter->filter;
 2168 
 2169     for (i = 0; i < num; i++) {
 2170         ptr[i].inscope = TRUE;
 2171         ptr[i].next = (struct t_filter *) 0;
 2172         if (ptr[i].scope != NULL) {
 2173             if (!match_group_list(group->name, ptr[i].scope)) {
 2174                 ptr[i].inscope = FALSE;
 2175                 inscope--;
 2176             }
 2177         }
 2178         if (i != 0 && ptr[i].inscope)
 2179             prev = prev->next = &ptr[i];
 2180     }
 2181     return inscope;
 2182 }
 2183 
 2184 
 2185 /*
 2186  * This will come in useful for filtering on non-overview hdr fields
 2187  */
 2188 #if 0
 2189 static FILE *
 2190 open_xhdr_fp(
 2191     char *header,
 2192     long min,
 2193     long max)
 2194 {
 2195 #   ifdef NNTP_ABLE
 2196     if (read_news_via_nntp && !read_saved_news && nntp_caps.hdr_cmd) {
 2197         char buf[NNTP_STRLEN];
 2198 
 2199         snprintf(buf, sizeof(buf), "%s %s %ld-%ld", nntp_caps.hdr_cmd, header, min, max);
 2200         return (nntp_command(buf, OK_HEAD, NULL, 0));
 2201     } else
 2202 #   endif /* NNTP_ABLE */
 2203         return (FILE *) 0;      /* Some trick implementation for local spool... */
 2204 }
 2205 #endif /* 0 */