"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.6.1/src/filter.c" (22 Dec 2021, 60262 Bytes) of package /linux/misc/tin-2.6.1.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.6.0_vs_2.6.1.

A hint: This file contains one or more very long lines, so maybe it is better readable using the pure text view mode that shows the contents as wrapped lines within the browser window.


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