1 /*
2  This file is part of SLRN.
3 
4  Copyright (c) 1994, 1999 John E. Davis <davis@space.mit.edu>
5 
6  This program is free software; you can redistribute it and/or modify it
7  under the terms of the GNU General Public License as published by the Free
8  Software Foundation; either version 2 of the License, or (at your option)
9  any later version.
10 
11  This program is distributed in the hope that it will be useful, but WITHOUT
12  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
14  more details.
15 
16  You should have received a copy of the GNU General Public License along
17  with this program; if not, write to the Free Software Foundation, Inc., 675
18  Mass Ave, Cambridge, MA 02139, USA.
19 */
20 #include "config.h"
21 #ifndef SLRNPULL_CODE
22 # include "slrnfeat.h"
23 #endif
24 
25 #include <stdio.h>
26 #include <string.h>
27 #include <sys/types.h>
28 #include <time.h>
29 
30 #ifdef HAVE_STDLIB_H
31 # include <stdlib.h>
32 #endif
33 
34 #include <slang.h>
35 #include "jdmacros.h"
36 
37 #ifndef SLRNPULL_CODE
38 # include "slrn.h"
39 # include "group.h"
40 # include "misc.h"
41 # include "server.h"
42 # include "hash.h"
43 # include "util.h"
44 #endif
45 
46 #ifdef KANJI
47 #include "chmap.h"
48 #endif
49 #include "art.h"
50 #include "xover.h"
51 #include "score.h"
52 
53 /* The score file will implement article scoring for slrn.  Basically the idea
54  * is that the user will provide a set of regular expressions that
55  * will act on the header of an article and return an integer or
56  * 'score'. If this score is less than zero, the article will be
57  * killed.  If it is a positive number greater than some user defined
58  * value, the article will be flagged as interesting.  If the score
59  * is zero, the article is not killed and it is not flagged as
60  * interesting either.
61  */
62 
63 int Slrn_Perform_Scoring = SLRN_XOVER_SCORING | SLRN_EXPENSIVE_SCORING;
64 
65 /* These two structures are pseudo-score types containing no compiled
66  * regular expressions.
67  */
68 typedef struct PScore_Regexp_Type
69 {
70 #define MAX_KEYWORD_LEN 24
71    char keyword[MAX_KEYWORD_LEN];      /* used only by generic type */
72    unsigned int header_type;
73 #define SCORE_SUBJECT 1
74 #define SCORE_FROM 2
75 #define SCORE_XREF 3
76 #define SCORE_NEWSGROUP 4
77 #define SCORE_REFERENCES 5
78 #define SCORE_LINES 6
79 #define SCORE_MESSAGE_ID 7
80 #define SCORE_DATE 8
81    /* generic requires extra server interaction */
82 #define SCORE_GENERIC 16
83    unsigned int flags;
84 #define NOT_FLAG 1
85 #define USE_INTEGER 2
86    union
87      {
88 	unsigned char *regexp_str;
89 	int ival;
90      }
91    ireg;
92    struct PScore_Regexp_Type *next;
93 }
94 PScore_Regexp_Type;
95 
96 typedef struct PScore_Type
97 {
98    int score;
99    unsigned int flags;
100 #define RETURN_THIS_SCORE 1
101 #define SCORE_IS_OR_TYPE 2
102    struct PScore_Regexp_Type *pregexp_list;
103    struct PScore_Type *next;
104 }
105 PScore_Type;
106 
107 typedef struct
108 {
109    SLRegexp_Type regexp;
110    unsigned char buf[512];	       /* for compiled pattern */
111 }
112 Our_SLRegexp_Type;
113 
114 /* These two structures are compiled versions of the above. */
115 typedef struct Score_Regexp_Type
116 {
117    unsigned int header_type;
118    char *generic_keyword;	       /* pointer to space in pscore */
119    int not_flag;
120    int do_osearch;
121    union
122      {
123 	int ival;		       /* used by certain headers */
124 	Our_SLRegexp_Type re;
125 	SLsearch_Type se;
126      }
127    search;
128    struct Score_Regexp_Type *next;
129 }
130 Score_Regexp_Type;
131 
132 typedef struct Score_Type
133 {
134    PScore_Type *pscore;		       /* points at structure this is derived from */
135    struct Score_Regexp_Type regexp_list;
136    struct Score_Type *next;
137 }
138 Score_Type;
139 
140 #define MAX_GROUP_REGEXP_SIZE	256
141 #define MAX_GROUP_NAME_LEN	80
142 
143 typedef struct Group_Score_Name_Type
144 {
145    SLRegexp_Type group_regexp;
146    char name[MAX_GROUP_NAME_LEN];      /* group name or pattern */
147    unsigned char buf[MAX_GROUP_REGEXP_SIZE];/* for compiled pattern */
148    struct Group_Score_Name_Type *next;
149 }
150 Group_Score_Name_Type;
151 
152 typedef struct Group_Score_Type
153 {
154    unsigned int gst_not_flag;
155    Group_Score_Name_Type gsnt;
156    PScore_Type *pst;
157    struct Group_Score_Type *next;
158 }
159 Group_Score_Type;
160 
161 static Group_Score_Type *Group_Score_Root;
162 static Group_Score_Type *Group_Score_Next;
163 static Score_Type *Score_Root, *Score_Tail;
164 
165 int Slrn_Apply_Score = 1;
166 
slrn_score_header(Slrn_Header_Type * h,char * newsgroup)167 int slrn_score_header (Slrn_Header_Type *h, char *newsgroup)
168 {
169    Score_Type *st;
170    int score = 0;
171    char *s;
172    int ival = 0;
173 
174 #ifndef SLRNPULL_CODE
175 #if SLRN_HAS_MSGID_CACHE
176    s = slrn_is_msgid_cached (h->msgid, newsgroup, 1);
177    if (s != NULL)
178      {
179 	/* Kill it if this wasn't the newsgroup where we saw it */
180 	if (strcmp (s, newsgroup))
181 	  return Slrn_Kill_Score_Max;
182      }
183 #endif
184 #endif				       /* NOT SLRNPULL_CODE */
185 
186    s = NULL;
187 
188    st = Score_Root;
189    while (st != NULL)
190      {
191 	unsigned int len;
192 	Score_Regexp_Type *srt = &st->regexp_list;
193 #ifndef SLRNPULL_CODE
194 	char buf[1024];
195 #endif
196 	int or_type = st->pscore->flags & SCORE_IS_OR_TYPE;
197 
198 	while (srt != NULL)
199 	  {
200 	     switch (srt->header_type)
201 	       {
202 		case SCORE_LINES:
203 		  ival = h->lines;
204 		  goto integer_compare;/* ugly, ugly, ugly!! */
205 
206 		case SCORE_SUBJECT:
207 		  s = h->subject;
208 		  break;
209 
210 		case SCORE_FROM:
211 		  s = h->from;
212 		  break;
213 
214 		case SCORE_DATE:
215 		  s = h->date;
216 		  break;
217 
218 		case SCORE_MESSAGE_ID:
219 		  s = h->msgid;
220 		  break;
221 
222 		case SCORE_XREF:
223 		  s = h->xref;
224 		  break;
225 
226 		case SCORE_REFERENCES:
227 		  s = h->refs;
228 		  break;
229 
230 		case SCORE_NEWSGROUP:
231 		  s = newsgroup;
232 		  break;
233 
234 		case SCORE_GENERIC:
235 #ifdef SLRNPULL_CODE
236 		  s = slrn_get_extra_xover_header (srt->generic_keyword);
237 #else
238 
239 		  /* Yuk.  This will be slow! */
240 		  if (Slrn_Score_After_XOver)
241 		    {
242 		       if (((Slrn_Perform_Scoring & SLRN_EXPENSIVE_SCORING) == 0)
243 			   || (-1 == Slrn_Server_Obj->sv_xhdr_command (srt->generic_keyword,
244 							h->number, buf, sizeof(buf))))
245 			 s = NULL;
246 		       else s = buf;
247 		    }
248 		  else
249 		    {
250 		       if (Slrn_Perform_Scoring & SLRN_EXPENSIVE_SCORING)
251 			 s = slrn_get_extra_xover_header (srt->generic_keyword);
252 		       else s = NULL;
253 		    }
254 #endif
255 		  break;
256 
257 		default:
258 		  s = NULL;	       /* not supposed to happen */
259 	       }
260 
261 	     if (s == NULL)
262 	       {
263 		  if (srt->not_flag)
264 		    {
265 		       /* Match */
266 		       if (or_type)
267 			 break;
268 
269 		       goto next_srt;
270 		    }
271 
272 		  /* Match failed */
273 		  if (or_type) goto next_srt;
274 		  break;
275 	       }
276 
277 	     len = strlen (s);
278 
279 	     if (srt->do_osearch)
280 	       {
281 		  SLsearch_Type *se = &srt->search.se;
282 
283 		  if ((len < (unsigned int) se->key_len)
284 		      || (NULL == SLsearch ((unsigned char *) s,
285 					    (unsigned char *) s + len,
286 					    se)))
287 		    {
288 		       if (srt->not_flag == 0)
289 			 {
290 			    if (or_type) goto next_srt;
291 			    break;
292 			 }
293 		    }
294 		  else if (srt->not_flag)
295 		    {
296 		       if (or_type) goto next_srt;
297 		       break;
298 		    }
299 	       }
300 	     else
301 	       {
302 		  SLRegexp_Type *re;
303 
304 		  re = &srt->search.re.regexp;
305 		  if ((len < re->min_length)
306 		      || (NULL == SLang_regexp_match ((unsigned char *)s, len, re)))
307 		    {
308 		       if (srt->not_flag == 0)
309 			 {
310 			    if (or_type) goto next_srt;
311 			    break;
312 			 }
313 		    }
314 		  else if (srt->not_flag)
315 		    {
316 		       if (or_type) goto next_srt;
317 		       break;
318 		    }
319 	       }
320 	     /* Get here if above matched */
321 	     if (or_type) break;
322 	     srt = srt->next;
323 	     continue;
324 
325 	     /* This is ugly but I am worried about speed. --- we only get
326 	      * here for those headers that have integer values.
327 	      */
328 	     integer_compare:
329 	     if (ival < srt->search.ival)
330 	       {
331 		  if (srt->not_flag == 0)
332 		    {
333 		       if (or_type) goto next_srt;
334 		       break;
335 		    }
336 	       }
337 	     else if (srt->not_flag)
338 	       {
339 		  if (or_type) goto next_srt;
340 		  break;
341 	       }
342 
343 	     /* If we get here, the regular expression matched. */
344 	     if (or_type) break;
345 
346 	     next_srt:
347 	     srt = srt->next;
348 	  }
349 
350 	if (((srt == NULL) && (or_type == 0))
351 	    || (or_type && (srt != NULL)))
352 	  {
353 	     int st_score = st->pscore->score;
354 
355 	     if (st->pscore->flags & RETURN_THIS_SCORE)
356 	       return st_score;
357 
358 	     if ((st_score == 9999)
359 		 || (st_score == -9999))
360 	       return st_score;
361 
362 	     if (st_score == 0)
363 	       return score;
364 
365 	     score += st_score;
366 	  }
367 
368 	st = st->next;
369      }
370    return score;
371 }
372 
373 
chain_group_regexp(PScore_Type * pst,int * generic)374 static int chain_group_regexp (PScore_Type *pst, int *generic)
375 {
376    PScore_Regexp_Type *psrt;
377    Score_Regexp_Type *srt;
378    Score_Type *st;
379    SLRegexp_Type *re;
380 
381    while (pst != NULL)
382      {
383 	st = (Score_Type *) slrn_malloc (sizeof (Score_Type), 1, 0);
384 	if (st == NULL)
385 	  return -1;
386 
387 	if (Score_Root == NULL)
388 	  {
389 	     Score_Root = st;
390 	  }
391 	else Score_Tail->next = st;
392 
393 	Score_Tail = st;
394 
395 	st->pscore = pst;
396 
397 	psrt = pst->pregexp_list;
398 	srt = &st->regexp_list;
399 
400 	while (psrt != NULL)
401 	  {
402 	     unsigned int flags = psrt->flags;
403 
404 	     if (SCORE_GENERIC == (srt->header_type = psrt->header_type))
405 	       *generic += 1;
406 
407 	     srt->generic_keyword = psrt->keyword;
408 	     srt->not_flag = (0 != (flags & NOT_FLAG));
409 
410 	     if (flags & USE_INTEGER)
411 	       {
412 		  srt->search.ival = psrt->ireg.ival;
413 	       }
414 	     else
415 	       {
416 		  re = &srt->search.re.regexp;
417 		  re->pat = psrt->ireg.regexp_str;
418 		  re->buf = srt->search.re.buf;
419 		  re->buf_len = sizeof (srt->search.re.buf);
420 		  re->case_sensitive = 0;
421 
422 		  if (0 != SLang_regexp_compile (re))
423 		    {
424 		       return -1;
425 		    }
426 
427 		  /* If an ordinary search is ok, use it. */
428 		  if (re->osearch)
429 		    {
430 		       srt->do_osearch = 1;
431 		       SLsearch_init ((char *) psrt->ireg.regexp_str, 1, 0, &srt->search.se);
432 		    }
433 	       }
434 
435 	     psrt = psrt->next;
436 	     if (psrt == NULL) break;
437 
438 	     srt->next = (Score_Regexp_Type *) slrn_malloc (sizeof (Score_Regexp_Type),
439 							    1, 0);
440 	     if (NULL == (srt = srt->next))
441 	       return -1;
442 	  }
443 	pst = pst->next;
444      }
445    return 0;
446 }
447 
free_group_chain(void)448 static void free_group_chain (void)
449 {
450    Score_Regexp_Type *srt;
451 
452    while (Score_Root != NULL)
453      {
454 	Score_Type *next = Score_Root->next;
455 	srt = &Score_Root->regexp_list;
456 	srt = srt->next;	       /* first not malloced */
457 	while (srt != NULL)
458 	  {
459 	     Score_Regexp_Type *srt_next = srt->next;
460 	     SLFREE (srt);
461 	     srt = srt_next;
462 	  }
463 	SLFREE (Score_Root);
464 	Score_Root = next;
465      }
466    Score_Tail = NULL;
467 }
468 
slrn_open_score(char * group_name)469 int slrn_open_score (char *group_name)
470 {
471    Group_Score_Type *gsc = Group_Score_Root;
472    unsigned int n;
473    int generic;
474 
475    free_group_chain ();
476 
477    if ((Slrn_Perform_Scoring == 0) || (Group_Score_Root == NULL))
478      {
479 	Slrn_Apply_Score = 0;
480 	return 0;
481      }
482 
483    n = strlen (group_name);
484    generic = 0;
485    while (gsc != NULL)
486      {
487 	Group_Score_Name_Type *gsnt;
488 	int match;
489 
490 	gsnt = &gsc->gsnt;
491 	match = 0;
492 	while ((gsnt != NULL) && (match == 0))
493 	  {
494 	     SLRegexp_Type *re = &gsnt->group_regexp;
495 
496 	     match = ((re->min_length <= n)
497 		      && (NULL != SLang_regexp_match ((unsigned char *) group_name, n, re)));
498 	     gsnt = gsnt->next;
499 	  }
500 
501 	if (gsc->gst_not_flag)
502 	  match = !match;
503 
504 	if (match)
505 	  {
506 	     if (-1 == chain_group_regexp (gsc->pst, &generic))
507 	       {
508 		  free_group_chain ();
509 		  return -1;
510 	       }
511 	  }
512 	gsc = gsc->next;
513      }
514 
515    if (Score_Root == NULL) return 0;
516 
517    Slrn_Apply_Score = 1;
518 
519    if ((generic == 0)
520        || (0 == (Slrn_Perform_Scoring & SLRN_EXPENSIVE_SCORING)))
521      return 1;
522 
523 #ifndef SLRNPULL_CODE
524    if ((generic >= 1) || !Slrn_Server_Obj->sv_has_xover || !Slrn_Server_Obj->sv_has_cmd ("XHDR"))
525      {
526 	Slrn_Score_After_XOver = 0;
527 	slrn_open_suspend_xover ();
528      }
529 #endif
530 
531    return 1;
532 }
533 
slrn_close_score(void)534 void slrn_close_score (void)
535 {
536    free_group_chain ();
537 #ifndef SLRNPULL_CODE
538    slrn_close_suspend_xover ();
539 #endif
540 }
541 
542 
543 
add_group_regexp(PScore_Type * pst,unsigned char * str,unsigned char * keyword,unsigned int type,int not_flag)544 static int add_group_regexp (PScore_Type *pst,
545 			     unsigned char *str,
546 			     unsigned char *keyword, unsigned int type, int not_flag)
547 {
548    unsigned int len;
549    PScore_Regexp_Type *psrt;
550 
551    *str++ = 0;			       /* null terminate keyword by zeroing
552 					* out the colon.  This is by agreement
553 					* with the calling routine.
554 					*/
555 
556    if (*str == ' ') str++;	       /* space following colon not meaningful */
557 
558    len = (unsigned int) (slrn_trim_string ((char *) str) - (char *) str);
559    if (0 == len) return -1;
560 
561    psrt = (PScore_Regexp_Type *) slrn_safe_malloc (sizeof (PScore_Regexp_Type));
562 
563    if (type != SCORE_LINES)
564      psrt->ireg.regexp_str = (unsigned char *) slrn_safe_strmalloc ((char *)str);
565    else
566      {
567 	psrt->ireg.ival = atoi((char *)str);
568 	psrt->flags |= USE_INTEGER;
569      }
570 
571    psrt->header_type = type;
572    strncpy (psrt->keyword, (char *) keyword, MAX_KEYWORD_LEN);
573    psrt->keyword[MAX_KEYWORD_LEN - 1] = 0;
574 
575    if (not_flag) psrt->flags |= NOT_FLAG;
576    psrt->next = NULL;
577    if (pst->pregexp_list == NULL)
578      {
579 	pst->pregexp_list = psrt;
580      }
581    else
582      {
583 	PScore_Regexp_Type *last = pst->pregexp_list;
584 	while (last->next != NULL) last = last->next;
585 	last->next = psrt;
586      }
587    return 0;
588 }
589 
590 
compile_group_names(char * group,Group_Score_Type * gst)591 static int compile_group_names (char *group, Group_Score_Type *gst)
592 {
593    char *comma;
594    Group_Score_Name_Type *gsnt;
595    unsigned int num_processed = 0;
596 
597    gsnt = &gst->gsnt;
598 
599    comma = group;
600    while (comma != NULL)
601      {
602 	SLRegexp_Type *re;
603 
604 	group = slrn_skip_whitespace (group);
605 
606 	comma = slrn_strchr ((char *) group, ',');
607 	if (comma != NULL)
608 	  {
609 	     *comma++ = 0;
610 
611 	     while (1)
612 	       {
613 		  comma = slrn_skip_whitespace (comma);
614 		  if (*comma == 0)
615 		    {
616 		       comma = NULL;
617 		       break;
618 		    }
619 		  if (*comma != ',')
620 		    break;
621 		  comma++;
622 	       }
623 	  }
624 
625 	(void) slrn_trim_string (group);
626 	if (*group == 0) continue;
627 
628 	strncpy (gsnt->name, group, MAX_GROUP_NAME_LEN - 1);
629 	/* Note: because of the memset, this string is null terminated. */
630 
631 	re = &gsnt->group_regexp;
632 	re->pat = (unsigned char *) slrn_fix_regexp ((char *)group);
633 	re->buf = gsnt->buf;
634 	re->buf_len = MAX_GROUP_REGEXP_SIZE;
635 	re->case_sensitive = 0;
636 	if (0 != SLang_regexp_compile (re))
637 	  {
638 	     return -1;
639 	  }
640 
641 	if (comma != NULL)
642 	  {
643 	     Group_Score_Name_Type *gsnt1;
644 
645 	     gsnt1 = (Group_Score_Name_Type *) slrn_safe_malloc (sizeof (Group_Score_Name_Type));
646 
647 	     gsnt->next = gsnt1;
648 	     gsnt = gsnt1;
649 	     group = comma;
650 	  }
651 	num_processed++;
652      }
653    if (num_processed) return 0;
654    return -1;
655 }
656 
create_new_score(unsigned char * group,int new_group_flag,unsigned int gst_not_flag,int score,unsigned int pscore_flags)657 static PScore_Type *create_new_score (unsigned char *group, int new_group_flag, unsigned int gst_not_flag,
658 				      int score, unsigned int pscore_flags)
659 {
660    PScore_Type *pst;
661 
662    if (new_group_flag)
663      {
664 	Group_Score_Type *gst, *tail;
665 
666 	tail = Group_Score_Next = Group_Score_Root;
667 	while (Group_Score_Next != NULL)
668 	  {
669 	     if ((Group_Score_Next->gsnt.next == NULL)
670 		 && (Group_Score_Next->gst_not_flag == gst_not_flag)
671 		 && !strcmp ((char *) group, Group_Score_Next->gsnt.name))
672 	       break;
673 	     tail = Group_Score_Next;
674 	     Group_Score_Next = Group_Score_Next->next;
675 	  }
676 
677 	if (Group_Score_Next == NULL)
678 	  {
679 	     gst = (Group_Score_Type *) slrn_safe_malloc (sizeof (Group_Score_Type));
680 
681 	     if (-1 == compile_group_names ((char *)group, gst))
682 	       return NULL;
683 
684 	     gst->gst_not_flag = gst_not_flag;
685 
686 	     if (Group_Score_Root == NULL)
687 	       {
688 		  Group_Score_Root = gst;
689 	       }
690 	     else tail->next = gst;
691 
692 	     Group_Score_Next = gst;
693 	  }
694      }
695 
696    /* Now create the PseudoScore type and add it. */
697    pst = (PScore_Type *) slrn_safe_malloc (sizeof (PScore_Type));
698 
699    pst->score = score;
700    pst->flags = pscore_flags;
701 
702    if (Group_Score_Next->pst == NULL)
703      {
704 	Group_Score_Next->pst = pst;
705      }
706    else
707      {
708 	PScore_Type *last = Group_Score_Next->pst;
709 	while (last->next != NULL) last = last->next;
710 	last->next = pst;
711      }
712    return pst;
713 }
714 
715 
score_error(char * msg,char * line,unsigned int linenum,char * file)716 static void score_error (char *msg, char *line, unsigned int linenum, char *file)
717 {
718    slrn_error ("Error processing %s\nLine %u:\n%s\n%s\n",
719 	       file, linenum, line, msg);
720 }
721 
722 
723 
free_group_scores(void)724 static void free_group_scores (void)
725 {
726    while (Group_Score_Root != NULL)
727      {
728 	Group_Score_Type *gnext = Group_Score_Root->next;
729 	PScore_Type *pst = Group_Score_Root->pst;
730 	Group_Score_Name_Type *gsnt;
731 
732 	gsnt = Group_Score_Root->gsnt.next;
733 	while (gsnt != NULL)
734 	  {
735 	     Group_Score_Name_Type *next = gsnt->next;
736 	     SLFREE (gsnt);
737 	     gsnt = next;
738 	  }
739 
740 	while (pst != NULL)
741 	  {
742 	     PScore_Type *pnext = pst->next;
743 	     PScore_Regexp_Type *r = pst->pregexp_list;
744 
745 	     while (r != NULL)
746 	       {
747 		  PScore_Regexp_Type *rnext = r->next;
748 
749 		  if ((r->flags & USE_INTEGER) == 0)
750 		    slrn_free ((char *) r->ireg.regexp_str);
751 		  SLFREE (r);
752 		  r = rnext;
753 	       }
754 	     SLFREE (pst);
755 	     pst = pnext;
756 	  }
757 
758 	SLFREE (Group_Score_Root);
759 	Group_Score_Root = gnext;
760      }
761 }
762 
has_score_expired(unsigned char * s,unsigned long today)763 static int has_score_expired (unsigned char *s, unsigned long today)
764 {
765    unsigned long mm, dd, yyyy;
766    unsigned long score_time;
767 
768    s = (unsigned char *) slrn_skip_whitespace ((char *) s);
769    if (*s == 0) return 0;
770 
771    if (((3 != sscanf ((char *) s, "%lu/%lu/%lu", &mm, &dd, &yyyy))
772 	&& (3 != sscanf ((char *) s, "%lu-%lu-%lu", &dd, &mm, &yyyy)))
773        || (dd > 31)
774        || (mm > 12)
775        || (yyyy < 1900))
776      return -1;
777 
778    score_time = (yyyy - 1900) * 10000 + (mm - 1) * 100 + dd;
779    if (score_time > today) return 0;
780    return 1;
781 }
782 
get_today(void)783 static unsigned long get_today (void)
784 {
785    unsigned long mm, yy, dd;
786    time_t tloc;
787    struct tm *tm_struct;
788 
789    time (&tloc);
790    tm_struct = localtime (&tloc);
791    yy = tm_struct->tm_year;
792    mm = tm_struct->tm_mon;
793    dd = tm_struct->tm_mday;
794 
795    return yy * 10000 + mm * 100 + dd;
796 }
797 
798 typedef struct
799 {
800    unsigned char group[256];
801    unsigned long today;
802    int score;
803    int start_new_group;
804    int gnt_not_flag;
805    int start_new_score;
806    int score_has_expired;
807    unsigned int pscore_flags;
808    PScore_Type *pst;
809    SLPreprocess_Type pt;
810 }
811 Score_Context_Type;
812 
813 static int read_score_file_internal (char *, Score_Context_Type *);
814 
handle_include_line(char * file,char * line,Score_Context_Type * c)815 static int handle_include_line (char *file, char *line, Score_Context_Type *c)
816 {
817    char *dir, *f;
818    char buf [SLRN_MAX_PATH_LEN];
819    int status;
820 
821    line = slrn_skip_whitespace (line);
822 
823    if (*line == 0)
824      return -1;
825 
826    (void) slrn_trim_string (line);
827 
828 #if defined(IBMPC_SYSTEM)
829    slrn_os2_convert_path (line);
830 #endif
831 
832    /* Make a filename which is relative to dir of current file.
833     * Note that slrn_dircat will properly handle absolute filenames.
834     */
835 
836    f = slrn_basename (file);
837    if (NULL == (dir = slrn_strnmalloc (file, (unsigned int) (f - file), 1)))
838      return -1;
839 
840    if (-1 == slrn_dircat (dir, line, buf))
841      {
842 	slrn_free (dir);
843 	return -1;
844      }
845 
846    status = read_score_file_internal (buf, c);
847    slrn_free (dir);
848    return status;
849 }
850 
read_score_file_internal(char * file,Score_Context_Type * c)851 static int read_score_file_internal (char *file, Score_Context_Type *c)
852 {
853    char line[1024];
854    FILE *fp;
855    unsigned int linenum = 0;
856 
857    if (NULL == (fp = fopen (file, "r")))
858      {
859 	slrn_error ("Unable to open score file %s", file);
860 	return -1;
861      }
862 
863    while (fgets (line, sizeof (line) - 1, fp))
864      {
865 	unsigned char *lp;
866 	int not_flag;
867 
868 #ifdef KANJI
869 	/* editor �����ɤ� JIS �� SJIS �ξ�硢EUC �ˤ��롣*/
870 	if (Slrn_Editor_Code != EUC) {
871 	   slrn_conv_string(line, EUC);
872 	}
873 #endif
874 	linenum++;
875 
876 	if (0 == SLprep_line_ok (line, &c->pt))
877 	  continue;
878 
879 	lp = (unsigned char *) slrn_skip_whitespace (line);
880 	if ((*lp == '#') || (*lp == '%') || (*lp <= ' ')) continue;
881 
882 	if ((0 == slrn_case_strncmp (lp, (unsigned char *) "include", 7))
883 	    && ((lp[7] == ' ') || (lp[7] == '\t')))
884 	  {
885 	     if (0 == handle_include_line (file, (char *)lp + 7, c))
886 	       continue;
887 
888 	     score_error ("Error handling INCLUDE line", line, linenum, file);
889 	     goto error_return;
890 	  }
891 
892 	if (*lp == '[')
893 	  {
894 	     unsigned char *g, *gmax, ch;
895 	     g = c->group;
896 	     gmax = g + sizeof (c->group);
897 
898 	     lp++;
899 	     lp = (unsigned char *) slrn_skip_whitespace ((char *)lp);
900 
901 	     c->gnt_not_flag = 0;
902 	     if (*lp == '~')
903 	       {
904 		  c->gnt_not_flag = 1;
905 		  lp = (unsigned char *) slrn_skip_whitespace ((char *)lp + 1);
906 	       }
907 
908 	     while (((ch = *lp++) != 0)
909 		    && (ch != ']') && (g < gmax))
910 	       *g++ = ch;
911 
912 	     if ((ch != ']') || (g == gmax))
913 	       {
914 		  score_error ("Syntax Error.", line, linenum, file);
915 		  goto error_return;
916 	       }
917 	     *g = 0;
918 	     c->start_new_group = 1;
919 	     c->score_has_expired = 0;
920 	     c->start_new_score = 0;
921 	     continue;
922 	  }
923 
924 	if (!slrn_case_strncmp (lp, (unsigned char *)"Score:", 6))
925 	  {
926 	     unsigned char *lpp = lp + 6;
927 	     c->pscore_flags = 0;
928 	     if (*lpp == ':')
929 	       {
930 		  lpp++;
931 		  c->pscore_flags |= SCORE_IS_OR_TYPE;
932 	       }
933 	     lpp = (unsigned char *) slrn_skip_whitespace ((char *) lpp);
934 	     if (*lpp == '=')
935 	       {
936 		  c->pscore_flags |= RETURN_THIS_SCORE;
937 		  lpp++;
938 	       }
939 	     c->score = atoi ((char *)lpp);
940 	     c->start_new_score = 1;
941 	     continue;
942 	  }
943 
944 	if (c->start_new_score)
945 	  {
946 	     if (!slrn_case_strncmp (lp, (unsigned char *) "Expires:", 8))
947 	       {
948 		  int ret;
949 		  ret = has_score_expired (lp + 8, c->today);
950 		  if (ret == -1)
951 		    {
952 		       score_error ("Expecting 'Expires: MM/DD/YYYY' or 'Expires: DD-MM-YYYY'",
953 				    line, linenum, file);
954 		       goto error_return;
955 		    }
956 		  if (ret)
957 		    {
958 		       slrn_message ("%s has expired score on line %d",
959 				     file, linenum);
960 		       c->start_new_score = 0;
961 		       c->score_has_expired = 1;
962 		    }
963 		  else c->score_has_expired = 0;
964 		  continue;
965 	       }
966 
967 
968 	     if (NULL == (c->pst = create_new_score (c->group, c->start_new_group, c->gnt_not_flag,
969 						     c->score, c->pscore_flags)))
970 	       {
971 		  score_error ("Bad group regular expression.", line, linenum, file);
972 		  goto error_return;
973 	       }
974 	     c->start_new_group = 0;
975 	     c->start_new_score = 0;
976 	     c->score_has_expired = 0;
977 	  }
978 
979 	if (c->score_has_expired) continue;
980 
981 	if (c->pst == NULL)
982 	  {
983 	     score_error ("Expecting Score keyword.", line, linenum, file);
984 	     goto error_return;
985 	  }
986 
987 	if (*lp == '~')
988 	  {
989 	     not_flag = 1;
990 	     lp++;
991 	  }
992 	else not_flag = 0;
993 
994 	/* Otherwise the line is a kill one */
995 	if (!slrn_case_strncmp (lp, (unsigned char *)"Subject:", 8))
996 	  add_group_regexp (c->pst, lp + 7, lp, SCORE_SUBJECT, not_flag);
997 	else if (!slrn_case_strncmp (lp, (unsigned char *)"From:", 5))
998 	  add_group_regexp (c->pst, lp + 4, lp, SCORE_FROM, not_flag);
999 	else if (!slrn_case_strncmp (lp, (unsigned char *)"Xref:", 5))
1000 	  add_group_regexp (c->pst, lp + 4, lp, SCORE_XREF, not_flag);
1001 	else if (!slrn_case_strncmp (lp, (unsigned char *)"Newsgroup:", 10))
1002 	  add_group_regexp (c->pst, lp + 9, lp, SCORE_NEWSGROUP, not_flag);
1003 	else if (!slrn_case_strncmp (lp, (unsigned char *)"References:", 11))
1004 	  add_group_regexp (c->pst, lp + 10, lp, SCORE_REFERENCES, not_flag);
1005 	else if (!slrn_case_strncmp (lp, (unsigned char *)"Lines:", 6))
1006 	  add_group_regexp (c->pst, lp + 5, lp, SCORE_LINES, not_flag);
1007 	else if (!slrn_case_strncmp (lp, (unsigned char *)"Date:", 5))
1008 	  add_group_regexp (c->pst, lp + 5, lp, SCORE_DATE, not_flag);
1009 	else if (!slrn_case_strncmp (lp, (unsigned char *)"Message-Id:", 11))
1010 	  add_group_regexp (c->pst, lp + 10, lp, SCORE_MESSAGE_ID, not_flag);
1011 	else
1012 	  {
1013 	     unsigned char *lpp = lp;
1014 	     while (*lpp && (*lpp != ':')) lpp++;
1015 	     if (*lpp != ':')
1016 	       {
1017 		  score_error ("Missing COLON.", line, linenum, file);
1018 		  goto error_return;
1019 	       }
1020 
1021 	     add_group_regexp (c->pst, lpp, lp, SCORE_GENERIC, not_flag);
1022 	  }
1023      }
1024 
1025    fclose (fp);
1026    return 0;
1027 
1028    error_return:
1029    fclose (fp);
1030    return -1;
1031 }
1032 
1033 
slrn_read_score_file(char * file)1034 int slrn_read_score_file (char *file)
1035 {
1036    Score_Context_Type sc;
1037    int status;
1038 
1039    if (Group_Score_Root != NULL)
1040      {
1041 	free_group_scores ();
1042      }
1043 
1044    sc.today = get_today ();
1045    sc.score = 0;
1046    sc.start_new_group = 1;
1047    sc.gnt_not_flag = 0;
1048    sc.start_new_score = 0;
1049    sc.score_has_expired = 0;
1050    sc.pscore_flags = 0;
1051    sc.pst = NULL;
1052    sc.group[0] = '*';
1053    sc.group[1] = 0;
1054 
1055    (void) SLprep_open_prep (&sc.pt);
1056    status = read_score_file_internal (file, &sc);
1057    SLprep_close_prep (&sc.pt);
1058 
1059    if (status == -1)
1060      Slrn_Apply_Score = 0;
1061    else
1062      Slrn_Apply_Score = 1;
1063 
1064    return status;
1065 }
1066 
1067