"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.3/src/filter.c" (23 Nov 2018, 59517 Bytes) of package /linux/misc/tin-2.4.3.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.2_vs_2.4.3.

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