/* This file Copyright 1992, 1993 by Clifford A. Adams */ /* scorefile.c * * A simple "proof of concept" scoring file for headers. * (yeah, right. :) */ #include "EXTERN.h" #include "common.h" #ifdef SCORE /* if SCORE is undefined, no code should be compiled */ #include "list.h" #include "hash.h" #include "cache.h" #include "bits.h" /* absfirst */ #include "head.h" #include "search.h" /* regex matches */ #include "ngdata.h" #include "ng.h" #include "term.h" /* finish_command() */ #include "util.h" #include "util2.h" #include "env.h" /* getval */ #include "rt-util.h" #include "mempool.h" #include "score.h" /* shared stuff... */ #ifdef SCAN_ART #include "scanart.h" #include "samain.h" /* for sa_authscored macro */ #endif #include "url.h" #include "INTERN.h" #include "scorefile.h" #include "scorefile.ih" /* list of score array markers (in htype field of score entry) */ /* entry is a file marker. Score is the file level */ #define SF_FILE_MARK_START (-1) #define SF_FILE_MARK_END (-2) /* other misc. rules */ #define SF_KILLTHRESHOLD (-3) #define SF_NEWAUTHOR (-4) #define SF_REPLY (-5) static int sf_file_level INIT(0); /* how deep are we? */ static char sf_buf[LBUFLEN]; static char** sf_extra_headers = NULL; static int sf_num_extra_headers = 0; static bool sf_has_extra_headers; /* Must be called before any other sf_ routine (once for each group) */ void sf_init() { int i; char* s; int level; /* depth of newsgroup score file */ sf_num_entries = 0; level = 0; sf_extra_headers = NULL; sf_num_extra_headers = 0; /* initialize abbreviation list */ sf_abbr = (char**)safemalloc(256 * sizeof (char*)); bzero((char*)sf_abbr, 256 * sizeof (char*)); if (sf_verbose) printf("\nReading score files...\n") FLUSH; sf_file_level = 0; /* find # of levels */ strcpy(sf_buf,filexp("%C")); level = 0; for (s = sf_buf; *s; s++) if (*s == '.') level++; /* count dots in group name */ level++; /* the main read-in loop */ for (i = 0; i <= level; i++) if ((s = sf_get_filename(i)) != NULL) sf_do_file(s); /* do post-processing (set thresholds and detect extra header usage) */ sf_has_extra_headers = FALSE; /* set thresholds from the sf_entries */ reply_active = newauthor_active = kill_thresh_active = FALSE; for (i = 0; i < sf_num_entries; i++) { if (sf_entries[i].head_type >= HEAD_LAST) sf_has_extra_headers = TRUE; switch (sf_entries[i].head_type) { case SF_KILLTHRESHOLD: kill_thresh_active = TRUE; kill_thresh = sf_entries[i].score; if (sf_verbose) { int j; /* rethink? */ for (j = i+1; j < sf_num_entries; j++) if (sf_entries[j].head_type == SF_KILLTHRESHOLD) break; if (j == sf_num_entries) /* no later thresholds */ printf("killthreshold %d\n",kill_thresh) FLUSH; } break; case SF_NEWAUTHOR: newauthor_active = TRUE; newauthor = sf_entries[i].score; if (sf_verbose) { int j; /* rethink? */ for (j = i+1; j < sf_num_entries; j++) if (sf_entries[j].head_type == SF_NEWAUTHOR) break; if (j == sf_num_entries) /* no later newauthors */ printf("New Author score: %d\n",newauthor) FLUSH; } break; case SF_REPLY: reply_active = TRUE; reply_score = sf_entries[i].score; if (sf_verbose) { int j; /* rethink? */ for (j = i+1; j < sf_num_entries; j++) if (sf_entries[j].head_type == SF_REPLY) break; if (j == sf_num_entries) /* no later reply rules */ printf("Reply score: %d\n",reply_score) FLUSH; } break; } } } void sf_clean() { int i; for (i = 0; i < sf_num_entries; i++) { if (sf_entries[i].compex != NULL) { free_compex(sf_entries[i].compex); free(sf_entries[i].compex); } } mp_free(MP_SCORE1); /* free memory pool */ if (sf_abbr) { for (i = 0; i < 256; i++) if (sf_abbr[i]) { free(sf_abbr[i]); sf_abbr[i] = NULL; } free(sf_abbr); } if (sf_entries) free(sf_entries); sf_entries = NULL; for (i = 0; i < sf_num_extra_headers; i++) free(sf_extra_headers[i]); sf_num_extra_headers = 0; sf_extra_headers = NULL; } /* rename sf_num_entries (to ?) */ /* use macro instead of all the "sf_entries[sf_num_entries-1]"? * call it "sf_recent_entry" or "sf_last_entry"? */ void sf_grow() { int i; sf_num_entries++; if (sf_num_entries == 1) { sf_entries = (SF_ENTRY*)safemalloc(sizeof (SF_ENTRY)); } else { sf_entries = (SF_ENTRY*)saferealloc((char*)sf_entries, sf_num_entries * sizeof (SF_ENTRY)); } i = sf_num_entries-1; sf_entries[i].compex = NULL; /* init */ sf_entries[i].flags = 0; sf_entries[i].str1 = NULL; sf_entries[i].str2 = NULL; } /* Returns -1 if no matching extra header found, otherwise returns offset * into the sf_extra_headers array. */ int sf_check_extra_headers(head) char* head; /* header name, (without ':' character) */ { int i; char* s; static char lbuf[LBUFLEN]; /* convert to lower case */ safecpy(lbuf,head,sizeof lbuf - 1); for (s = lbuf; *s; s++) { if (isalpha(*s) && isupper(*s)) *s = tolower(*s); /* convert to lower case */ } for (i = 0; i < sf_num_extra_headers; i++) { if (strEQ(sf_extra_headers[i],lbuf)) return i; } return -1; } /* adds the header to the list of known extra headers if it is not already * known. */ void sf_add_extra_header(head) char* head; /* new header name, (without ':' character) */ { static char lbuf[LBUFLEN]; /* ick. */ int len; char* colonptr; /* points to ':' character */ char* s; char* s2; /* check to see if it's already known */ /* first see if it is a known system header */ safecpy(lbuf,head,sizeof lbuf - 2); len = strlen(lbuf); lbuf[len] = ':'; lbuf[len+1] = '\0'; colonptr = lbuf+len; if (set_line_type(lbuf,colonptr) != SOME_LINE) return; /* known types should be interpreted in normal way */ /* then check to see if it's a known extra header */ if (sf_check_extra_headers(head) >= 0) return; sf_num_extra_headers++; sf_extra_headers = (char**)saferealloc((char*)sf_extra_headers, sf_num_extra_headers * sizeof (char*)); s = savestr(head); for (s2 = s; *s2; s2++) { if (isalpha(*s2) && isupper(*s2)) *s2 = tolower(*s2); /* convert to lower case */ } sf_extra_headers[sf_num_extra_headers-1] = s; } char* sf_get_extra_header(art,hnum) ART_NUM art; /* article number to check */ int hnum; /* header number: offset into sf_extra_headers */ { char* s; char* head; /* header text */ int len; /* length of header */ static char lbuf[LBUFLEN]; parseheader(art); /* fast if already parsed */ head = sf_extra_headers[hnum]; len = strlen(head); for (s = headbuf; s && *s && *s != '\n'; s++) { if (strncaseEQ(head,s,len)) { s = index(s,':'); if (!s) return nullstr; s++; /* skip the colon */ while (*s == ' ' || *s == '\t') s++; if (!*s) return nullstr; head = s; /* now point to start of new text */ s = index(s,'\n'); if (!s) return nullstr; *s = '\0'; safecpy(lbuf,head,sizeof lbuf - 1); *s = '\n'; return lbuf; } s = index(s,'\n'); /* '\n' will be skipped on loop increment */ } return nullstr; } /* move to util.c ? */ /* Returns TRUE if text pointed to by s is a text representation of * the number 0. Used for error checking. * Note: does not check for trailing garbage ("+00kjsdfk" returns TRUE). */ bool is_text_zero(s) char* s; { return *s == '0' || ((*s == '+' || *s == '-') && s[1]=='0'); } /* keep this one outside the functions because it is shared */ static char sf_file[LBUFLEN]; /* filenames of type a/b/c/foo.bar.misc for group foo.bar.misc */ char* sf_get_filename(level) int level; { char* s; strcpy(sf_file,filexp(getval("SCOREDIR",DEFAULT_SCOREDIR))); strcat(sf_file,"/"); #ifdef SHORTSCORENAMES strcat(sf_file,filexp("%C")); s1 = rindex(sf_file,'/'); /* find last slash in filename */ if (!level) *s1 = '\0'; /* cut off slash for global */ i = level; while (i--) { *s1++ = '/'; while (*s1 != '.' && *s1 != '\0') s1++; if (*s1 == '\0' && i > 0) /* not enough levels exist */ return NULL; /* get_file2 wouldn't work either... */ *s1 = '\0'; } strcat(sf_file,"/SCORE"); #else /* !SHORTSCORENAMES */ if (!level) { /* allow environment variable later... */ strcat(sf_file,"global"); } else { strcat(sf_file,filexp("%C")); s = rindex(sf_file,'/'); /* maybe redo this logic later... */ while (level--) { if (*s == '\0') /* no more name to match */ return NULL; while (*s && *s != '.') s++; if (*s && level) s++; } *s = '\0'; /* cut end of score file */ } #endif /* SHORTSCORENAMES */ return sf_file; } /* given a string, if no slashes prepends SCOREDIR env. variable */ char* sf_cmd_fname(s) char* s; { static char lbuf[LBUFLEN]; char* s1; s1 = index(s,'/'); if (s1) return s; /* no slashes in this filename */ strcpy(lbuf,getval("SCOREDIR",DEFAULT_SCOREDIR)); strcat(lbuf,"/"); strcat(lbuf,s); return lbuf; } /* returns TRUE if good command, FALSE otherwise */ bool sf_do_command(cmd,check) char* cmd; /* text of command */ bool_int check; /* if TRUE, just check, don't execute */ { char* s; int i; char ch; if (strnEQ(cmd,"killthreshold",13)) { /* skip whitespace and = sign */ for (s = cmd+13; *s && (*s == ' ' || *s == '\t' || *s == '='); s++) ; /* make **sure** that there is a number here */ i = atoi(s); if (i == 0) /* it might not be a number */ if (!is_text_zero(s)) { printf("\nBad killthreshold: %s",cmd); return FALSE; /* continue looping */ } if (check) return TRUE; sf_grow(); sf_entries[sf_num_entries-1].head_type = SF_KILLTHRESHOLD; sf_entries[sf_num_entries-1].score = i; return TRUE; } if (strnEQ(cmd,"savescores",10)) { /* skip whitespace and = sign */ for (s = cmd+10; *s && (*s == ' ' || *s == '\t' || *s == '='); s++) ; if (strnEQ(s,"off",3)) { if (!check) sc_savescores = FALSE; return TRUE; } if (*s) { /* there is some argument */ if (check) return TRUE; sc_savescores = TRUE; return TRUE; } printf("Bad savescores command: |%s|\n",cmd) FLUSH; return FALSE; } if (strnEQ(cmd,"newauthor",9)) { /* skip whitespace and = sign */ for (s = cmd+9; *s && (*s == ' ' || *s == '\t' || *s == '='); s++) ; /* make **sure** that there is a number here */ i = atoi(s); if (i == 0) /* it might not be a number */ if (!is_text_zero(s)) { printf("\nBad newauthor: %s",cmd); return FALSE; /* continue looping */ } if (check) return TRUE; sf_grow(); sf_entries[sf_num_entries-1].head_type = SF_NEWAUTHOR; sf_entries[sf_num_entries-1].score = i; return TRUE; } if (strnEQ(cmd,"include",7)) { if (check) return TRUE; s = cmd+7; while (*s == ' ' || *s == '\t') s++; /* skip whitespace */ if (!*s) { printf("Bad include command (missing filename)\n"); return FALSE; } sf_do_file(filexp(sf_cmd_fname(s))); return TRUE; } if (strnEQ(cmd,"exclude",7)) { if (check) return TRUE; s = cmd+7; while (*s == ' ' || *s == '\t') s++; /* skip whitespace */ if (!*s) { printf("Bad exclude command (missing filename)\n"); return FALSE; } sf_exclude_file(filexp(sf_cmd_fname(s))); return TRUE; } if (strnEQ(cmd,"header",6)) { char* s2; s = cmd+7; while (*s == ' ' || *s == '\t') s++; /* skip whitespace */ for (s2 = s; *s2 && *s2 != ':'; s2++) ; if (!s2) { printf("\nBad header command (missing :)\n%s\n",cmd) FLUSH; return FALSE; } if (check) return TRUE; *s2 = '\0'; sf_add_extra_header(s); *s2 = ':'; return TRUE; } if (strnEQ(cmd,"begin",5)) { s = cmd+6; while (*s == ' ' || *s == '\t') s++; /* skip whitespace */ if (strnEQ(s,"score",5)) { /* do something useful later */ return TRUE; } return TRUE; } if (strnEQ(cmd,"reply",5)) { /* skip whitespace and = sign */ for (s = cmd+5; *s && (*s == ' ' || *s == '\t' || *s == '='); s++) ; /* make **sure** that there is a number here */ i = atoi(s); if (i == 0) /* it might not be a number */ if (!is_text_zero(s)) { printf("\nBad reply command: %s\n",cmd); return FALSE; /* continue looping */ } if (check) return TRUE; sf_grow(); sf_entries[sf_num_entries-1].head_type = SF_REPLY; sf_entries[sf_num_entries-1].score = i; return TRUE; } if (strnEQ(cmd,"file",4)) { if (check) return TRUE; s = cmd+4; while (*s == ' ' || *s == '\t') s++; /* skip whitespace */ if (!*s) { printf("Bad file command (missing parameters)\n"); return FALSE; } ch = *s++; while ((*s == ' ') || (*s == '\t')) s++; /* skip whitespace */ if (!*s) { printf("Bad file command (missing parameters)\n"); return FALSE; } if (sf_abbr[(int)ch]) free(sf_abbr[(int)ch]); sf_abbr[(int)ch] = savestr(sf_cmd_fname(s)); return TRUE; } if (strnEQ(cmd,"end",3)) { s = cmd+4; while (*s == ' ' || *s == '\t') s++; /* skip whitespace */ if (strnEQ(s,"score",5)) { /* do something useful later */ return TRUE; } return TRUE; } if (strnEQ(cmd,"newsclip",8)) { printf("Newsclip is no longer supported.\n") FLUSH; return FALSE; } /* no command matched */ printf("Unknown command: |%s|\n",cmd) FLUSH; return FALSE; } COMPEX* sf_compex INIT(NULL); char* sf_freeform(start1,end1) char* start1; /* points to first character of keyword */ char* end1; /* points to last character of keyword */ { char* s; bool error; char ch; error = FALSE; /* be optimistic :-) */ /* cases are # of letters in keyword */ switch (end1-start1+1) { case 7: if (strnEQ(start1,"pattern",7)) { sf_pattern_status = TRUE; break; } error = TRUE; break; case 4: #ifdef UNDEF /* here is an example of a hypothetical freeform key with an argument */ if (strnEQ(start1,"date",4)) { char* s1; int datenum; /* skip whitespace and = sign */ for (s = end1+1; *s && (*s == ' ' || *s == '\t'); s++) ; if (!*s) { /* ran out of line */ printf("freeform: date keyword: ran out of input\n"); return s; } datenum = atoi(s); printf("Date: %d\n",datenum) FLUSH; while (isdigit(*s)) s++; /* skip datenum */ end1 = s; /* end of key data */ break; } #endif error = TRUE; break; default: error = TRUE; break; } if (error) { s = end1+1; ch = *s; *s = '\0'; printf("Scorefile freeform: unknown key: |%s|\n",start1) FLUSH; *s = ch; return NULL; /* error indicated */ } /* no error, so skip whitespace at end of key */ for (s = end1+1; *s && (*s == ' ' || *s == '\t'); s++) ; return s; } bool sf_do_line(line,check) char* line; bool_int check; /* if TRUE, just check the line, don't act. */ { char ch; char* s; char* s2; int i,j; if (!line || !*line) return TRUE; /* very empty line */ s = line + strlen(line) - 1; if (*s == '\n') *s = '\0'; /* kill the newline */ ch = line[0]; if (ch == '#') /* comment */ return TRUE; /* reset any per-line bitflags */ sf_pattern_status = FALSE; if (isalpha(ch)) /* command line */ return sf_do_command(line,check); /* skip whitespace */ for (s = line; *s && (*s == ' ' || *s == '\t'); s++) ; if (!*s || *s == '#') return TRUE; /* line was whitespace or comment after whitespace */ /* convert line to lowercase (make optional later?) */ for (s2 = s; *s2 != '\0'; s2++) { if (isupper(*s2)) *s2 = tolower(*s2); /* convert to lower case */ } i = atoi(s); if (i == 0) { /* it might not be a number */ if (!is_text_zero(s)) { printf("\nBad scorefile line:\n|%s|\n",s); return FALSE; } } /* add the line as a scoring entry */ while (isdigit(*s) || *s == '+' || *s == '-' || *s == ' ' || *s == '\t') s++; /* skip score */ while (TRUE) { for (s2 = s; *s2 && !(*s2 == ' ' || *s2 == '\t'); s2++) ; s2--; if (*s2 == ':') /* did header */ break; /* go to set header routine */ s = sf_freeform(s,s2); if (!s || !*s) { /* used up all the line's text, or error */ printf("Scorefile entry error error (freeform parse). "); printf("Line was:\n|%s|\n",line) FLUSH; return FALSE; /* error */ } s2 = s; } /* while */ /* s is start of header name, s2 points to the ':' character */ j = set_line_type(s,s2); if (j == SOME_LINE) { *s2 = '\0'; j = sf_check_extra_headers(s); *s2 = ':'; if (j >= 0) j += HEAD_LAST; else { printf("Unknown score header type. Line follows:\n|%s|\n",line); return FALSE; } } /* skip whitespace */ for (s = ++s2; *s && (*s == ' ' || *s == '\t'); s++) ; if (!*s) { /* no pattern */ printf("Empty score pattern. Line follows:\n|%s|\n",line) FLUSH; return FALSE; } if (check) return TRUE; /* limits of check */ sf_grow(); /* acutally make an entry */ sf_entries[sf_num_entries-1].head_type = j; sf_entries[sf_num_entries-1].score = i; if (sf_pattern_status) { /* in pattern matching mode */ sf_entries[sf_num_entries-1].flags |= 1; sf_entries[sf_num_entries-1].str1 = mp_savestr(s,MP_SCORE1); sf_compex = (COMPEX*)safemalloc(sizeof (COMPEX)); init_compex(sf_compex); /* compile arguments: */ /* 1st is COMPEX to store compiled regex in */ /* 2nd is search string */ /* 3rd should be TRUE if the search string is a regex */ /* 4th is TRUE for case-insensitivity */ s2 = compile(sf_compex,s,TRUE,TRUE); if (s2 != NULL) { printf("Bad pattern : |%s|\n",s) FLUSH; printf("Compex returns: |%s|\n",s2) FLUSH; free_compex(sf_compex); free(sf_compex); sf_entries[sf_num_entries-1].compex = NULL; return FALSE; } else sf_entries[sf_num_entries-1].compex = sf_compex; } else { sf_entries[sf_num_entries-1].flags &= 0xfe; sf_entries[sf_num_entries-1].str2 = NULL; /* Note: consider allowing * wildcard on other header filenames */ if (j == FROM_LINE) { /* may have * wildcard */ if ((s2 = index(s,'*')) != NULL) { sf_entries[sf_num_entries-1].str2 = mp_savestr(s2+1,MP_SCORE1); *s2 = '\0'; } } sf_entries[sf_num_entries-1].str1 = mp_savestr(s,MP_SCORE1); } return TRUE; } void sf_do_file(fname) char* fname; { char* s; int sf_fp; int i; char* safefilename; #ifdef SCOREFILE_CACHE sf_fp = sf_open_file(fname); if (sf_fp < 0) return; #else fp = fopen(fname,"r"); if (!fp) return; #endif sf_file_level++; if (sf_verbose) { for (i = 1; i < sf_file_level; i++) printf("."); /* maybe later putchar... */ printf("Score file: %s\n",fname) FLUSH; } safefilename = savestr(fname); /* add end marker to scoring array */ sf_grow(); sf_entries[sf_num_entries-1].head_type = SF_FILE_MARK_START; /* file_level is 1 to n */ sf_entries[sf_num_entries-1].score = sf_file_level; sf_entries[sf_num_entries-1].str2 = NULL; sf_entries[sf_num_entries-1].str1 = savestr(safefilename); #ifdef SCOREFILE_CACHE while ((s = sf_file_getline(sf_fp)) != NULL) { strcpy(sf_buf,s); s = sf_buf; #else while ((s = fgets(sf_buf,1020,fp)) != NULL) { /* consider buffer size */ #endif (void)sf_do_line(s,FALSE); } #ifndef SCOREFILE_CACHE fclose(fp); #endif /* add end marker to scoring array */ sf_grow(); sf_entries[sf_num_entries-1].head_type = SF_FILE_MARK_END; /* file_level is 1 to n */ sf_entries[sf_num_entries-1].score = sf_file_level; sf_entries[sf_num_entries-1].str2 = NULL; sf_entries[sf_num_entries-1].str1 = savestr(safefilename); free(safefilename); sf_file_level--; } int score_match(str,ind) char* str; /* string to match on */ int ind; /* index into sf_entries */ { char* s1; char* s2; char* s3; s1 = sf_entries[ind].str1; s2 = sf_entries[ind].str2; if (sf_entries[ind].flags & 1) { /* pattern style match */ if (sf_entries[ind].compex != NULL) { /* we have a good pattern */ s2 = execute(sf_entries[ind].compex,str); if (s2 != NULL) return TRUE; } return FALSE; } /* default case */ if ((s3 = STRSTR(str,s1)) != NULL && (!s2 || STRSTR(s3+strlen(s1),s2))) return TRUE; return FALSE; } int sf_score(a) ART_NUM a; { int sum,i,j; int h; /* header type */ char* s; /* misc */ bool old_untrim; /* old value of untrim_cache */ if (is_unavailable(a)) return LOWSCORE; /* unavailable arts get low negative score. */ /* if there are no score entries, then the answer is real easy and quick */ if (sf_num_entries == 0) return 0; old_untrim = untrim_cache; untrim_cache = TRUE; sc_scoring = TRUE; /* loop prevention */ sum = 0; /* parse the header now if there are extra headers */ /* (This could save disk accesses.) */ if (sf_has_extra_headers) parseheader(a); for (i = 0; i < sf_num_entries; i++) { h = sf_entries[i].head_type; if (h <= 0) /* don't use command headers for scoring */ continue; /* the outer for loop */ /* if this head_type has been done before, this entry has already been done */ if (sf_entries[i].flags & 2) { /* rule has been applied */ sf_entries[i].flags &= 0xfd; /* turn off flag */ continue; /* ...with the next rule */ } /* sf_get_line will return ptr to buffer (already lowercased string) */ s = sf_get_line(a,h); if (!s || !*s) /* no such line for the article */ continue; /* with the sf_entries. */ /* do the matches for this header */ for (j = i; j < sf_num_entries; j++) { /* see if there is a match */ if (h == sf_entries[j].head_type) { if (j != i) { /* set flag only for future rules */ sf_entries[j].flags |= 2; /* rule has been applied. */ } if (score_match(s,j)) { sum = sum + sf_entries[j].score; if (h == FROM_LINE) article_ptr(a)->scoreflags |= SFLAG_AUTHOR; if (sf_score_verbose) sf_print_match(j); } } } } if (newauthor_active && !(article_ptr(a)->scoreflags & SFLAG_AUTHOR)) { sum = sum+newauthor; /* add new author bonus */ if (sf_score_verbose) { printf("New Author: %d\n",newauthor) FLUSH; /* consider: print which file the bonus came from */ } } if (reply_active) { /* should be in cache if a rule above used the subject */ s = fetchcache(a, SUBJ_LINE, TRUE); /* later: consider other possible reply forms (threading?) */ if (s && subject_has_Re(s,(char**)NULL)) { sum = sum+reply_score; if (sf_score_verbose) { printf("Reply: %d\n",reply_score); /* consider: print which file the bonus came from */ } } } untrim_cache = old_untrim; sc_scoring = FALSE; return sum; } /* returns changed score line or NULL if no changes */ char* sf_missing_score(line) char* line; { static char lbuf[LBUFLEN]; int i; char* s; /* save line since it is probably pointing at (the TRN-global) buf */ s = savestr(line); printf("Possibly missing score.\n\ Type a score now or delete the colon to abort this entry:\n") FLUSH; buf[0] = ':'; buf[1] = FINISHCMD; i = finish_command(TRUE); /* print the CR */ if (!i) { /* there was no score */ free(s); return NULL; } strcpy(lbuf,buf+1); i = strlen(lbuf); lbuf[i] = ' '; lbuf[i+1] = '\0'; strcat(lbuf,s); free(s); return lbuf; } /* Interprets the '\"' command for creating new score entries online */ /* consider using some external buffer rather than the 2 internal ones */ void sf_append(line) char* line; { char* scoreline; /* full line to add to scorefile */ char* scoretext; /* text after the score# */ char filechar; /* filename character from line */ char* filename; /* expanded filename */ static char filebuf[LBUFLEN]; FILE* fp; char ch; /* misc */ char* s; if (!line) return; /* do nothing with empty string */ filechar = *line; /* ch is file abbreviation */ if (filechar == '?') { /* list known file abbreviations */ int i; printf("List of abbreviation/file pairs\n") ; for (i = 0; i < 256; i++) if (sf_abbr[i]) printf("%c %s\n",(char)i,sf_abbr[i]) FLUSH; printf("\" [The current newsgroup's score file]\n") FLUSH; printf("* [The global score file]\n") FLUSH; return; } /* skip whitespace after filechar */ scoreline = line+1; while (*scoreline == ' ' || *scoreline == '\t') scoreline++; ch = *scoreline; /* first non-whitespace after filechar */ /* If the scorefile line does not begin with a number, and is not a valid command, request a score */ if (!isdigit(ch) && ch != '+' && ch != '-' && ch != ':' && ch != '!' && ch != '#') { if (!sf_do_line(scoreline,TRUE)) { /* just checking */ scoreline = sf_missing_score(scoreline); if (!scoreline) { /* no score typed */ printf("Score entry aborted.\n") FLUSH; return; } } } /* scoretext = first non-whitespace after score# */ for (scoretext = scoreline; isdigit(*scoretext) || *scoretext == '+' || *scoretext == '-' || *scoretext == ' ' || *scoretext == '\t'; scoretext++) { ; } /* special one-character shortcuts */ if (*scoretext && scoretext[1] == '\0') { static char lbuf[LBUFLEN]; switch(*scoretext) { case 'F': /* domain-shortened FROM line */ strcpy(lbuf,scoreline); lbuf[strlen(lbuf)-1] = '\0'; strcat(lbuf,filexp("from: %y")); scoreline = lbuf; break; case 'S': /* current subject */ strcpy(lbuf,scoreline); s = fetchcache(art,SUBJ_LINE,TRUE); if (!s || !*s) { printf("No subject: score entry aborted.\n"); return; } if (s[0] == 'R' && s[1] == 'e' && s[2] == ':' && s[3] == ' ') s += 4; /* change this next line if LBUFLEN changes */ sprintf(lbuf+(strlen(lbuf)-1),"subject: %.900s",s); scoreline = lbuf; break; default: printf("\nBad scorefile line: |%s| (not added)\n", line) FLUSH; return; } printf("%s\n",scoreline) FLUSH; } /* test the scoring line unless filechar is '!' (meaning do it now) */ if (!sf_do_line(scoreline,filechar!='!')) { printf("Bad score line (ignored)\n") FLUSH; return; } if (filechar == '!') return; /* don't actually append to file */ if (filechar == '"') { /* do local group */ /* Note: should probably be changed to use sf_ file functions */ strcpy(filebuf,getval("SCOREDIR",DEFAULT_SCOREDIR)); #ifdef SHORTSCORENAMES strcat(filebuf,"/%c/SCORE"); #else strcat(filebuf,"/%C"); #endif filename = filebuf; } else if (filechar == '*') { /* do global scorefile */ /* Note: should probably be changed to use sf_ file functions */ strcpy(filebuf,getval("SCOREDIR",DEFAULT_SCOREDIR)); strcat(filebuf,"/global"); filename = filebuf; } else if (!(filename = sf_abbr[(int)filechar])) { printf("\nBad file abbreviation: %c\n",filechar) FLUSH; return; } filename = filexp(sf_cmd_fname(filename)); /* allow shortcuts */ /* make sure directory exists... */ makedir(filename,MD_FILE); #ifdef SCOREFILE_CACHE sf_file_clear(); #endif if ((fp = fopen(filename,"a")) != NULL) { /* open (or create) for append */ fprintf(fp,"%s\n",scoreline); fclose(fp); } else /* unsuccessful in opening file */ printf("\nCould not open (for append) file %s\n",filename); return; } /* returns a lowercased copy of the header line type h in private buffer */ char* sf_get_line(a,h) ART_NUM a; int h; { static char sf_getline[LBUFLEN]; char* s; if (h <= SOME_LINE) { printf("sf_get_line(%d,%d): bad header type\n",(int)a,h) FLUSH; printf("(Internal error: header number too low)\n") FLUSH; *sf_getline = '\0'; return sf_getline; } if (h >= HEAD_LAST) { if (h-HEAD_LAST < sf_num_extra_headers) s = sf_get_extra_header(a,h-HEAD_LAST); else { printf("sf_get_line(%d,%d): bad header type\n",(int)a,h) FLUSH; printf("(Internal error: header number too high)\n") FLUSH; *sf_getline = '\0'; return sf_getline; } } else if (h == SUBJ_LINE) s = fetchcache(a,h,TRUE); /* get compressed copy */ else s = prefetchlines(a,h,FALSE); /* don't make a copy */ if (!s) *sf_getline = '\0'; else safecpy(sf_getline,s,sizeof sf_getline - 1); for (s = sf_getline; *s; s++) if (isupper(*s)) *s = tolower(*s); *s = tolower(*s); return sf_getline; } /* given an index into sf_entries, print information about that index */ void sf_print_match(indx) int indx; { int i,j,k; int level,tmplevel; /* level is initialized iff used */ char* head_name; char* pattern; for (i = indx; i >= 0; i--) { j = sf_entries[i].head_type; if (j == SF_FILE_MARK_START) /* found immediate inclusion. */ break; if (j == SF_FILE_MARK_END) { /* found included file, skip */ tmplevel = sf_entries[i].score; for (k = i; k >= 0; k--) { if (sf_entries[k].head_type == SF_FILE_MARK_START && sf_entries[k].score == tmplevel) break; /* inner for loop */ } i = k; /* will be decremented again */ } } if (i >= 0) level = sf_entries[i].score; /* print the file markers. */ for ( ; i >= 0; i--) { if (sf_entries[i].head_type == SF_FILE_MARK_START && sf_entries[i].score <= level) { level--; /* go out... */ for (k = 0; k < level; k++) printf("."); /* make putchar later? */ printf("From file: %s\n",sf_entries[i].str1); if (level == 0) /* top level */ break; /* out of the big for loop */ } } if (sf_entries[indx].flags & 1) /* regex type */ pattern = "pattern "; else pattern = ""; if (sf_entries[indx].head_type >= HEAD_LAST) head_name = sf_extra_headers[sf_entries[indx].head_type-HEAD_LAST]; else head_name = htype[sf_entries[indx].head_type].name; printf("%d %s%s: %s", sf_entries[indx].score,pattern,head_name, sf_entries[indx].str1); if (sf_entries[indx].str2) printf("*%s",sf_entries[indx].str2); printf("\n"); } void sf_exclude_file(fname) char* fname; { int start,end; int newnum; SF_ENTRY* tmp_entries; for (start = 0; start < sf_num_entries; start++) if (sf_entries[start].head_type == SF_FILE_MARK_START && strEQ(sf_entries[start].str1,fname)) break; if (start == sf_num_entries) { printf("Exclude: file |%s| was not included\n",fname) FLUSH; return; } for (end = start+1; end < sf_num_entries; end++) if (sf_entries[end].head_type==SF_FILE_MARK_END && strEQ(sf_entries[end].str1,fname)) break; if (end == sf_num_entries) { printf("Exclude: file |%s| is incomplete at exclusion command\n", fname) FLUSH; /* insert more explanation later? */ return; } newnum = sf_num_entries-(end-start)-1; #ifdef UNDEF /* Deal with exclusion of all scorefile entries. * This cannot happen since the exclusion command has to be within a * file. Code kept in case online exclusions allowed later. */ if (newnum==0) { sf_num_entries = 0; free(sf_entries); sf_entries = NULL; return; } #endif tmp_entries = (SF_ENTRY*)safemalloc(newnum*sizeof(SF_ENTRY)); /* copy the parts into tmp_entries */ if (start > 0) bcopy((char*)sf_entries,(char*)tmp_entries,start * sizeof (SF_ENTRY)); if (end < sf_num_entries-1) bcopy((char*)(sf_entries+end+1), (char*)(tmp_entries+start), (sf_num_entries-end-1) * sizeof (SF_ENTRY)); free(sf_entries); sf_entries = tmp_entries; sf_num_entries = newnum; if (sf_verbose) printf("Excluded file: %s\n",fname) FLUSH; } void sf_edit_file(filespec) char* filespec; /* file abbrev. or name */ { char filebuf[LBUFLEN]; /* clean up buffers */ char filechar; /* which file to do? */ char* fname_noexpand; /* non-expanded filename */ if (!filespec || !*filespec) return; /* empty, do nothing (error later?) */ filechar = *filespec; /* if more than one character use as filename */ if (filespec[1]) strcpy(filebuf,filespec); else if (filechar == '"') { /* edit local group */ /* Note: should probably be changed to use sf_ file functions */ strcpy(filebuf,getval("SCOREDIR",DEFAULT_SCOREDIR)); #ifdef SHORTSCORENAMES strcat(filebuf,"/%c/SCORE"); #else strcat(filebuf,"/%C"); #endif } else if (filechar == '*') { /* edit global scorefile */ /* Note: should probably be changed to use sf_ file functions */ strcpy(filebuf,getval("SCOREDIR",DEFAULT_SCOREDIR)); strcat(filebuf,"/global"); } else { /* abbreviation */ if (!sf_abbr[(int)filechar]) { printf("\nBad file abbreviation: %c\n",filechar) FLUSH; return; } strcpy(filebuf,sf_abbr[(int)filechar]); } fname_noexpand = sf_cmd_fname(filebuf); strcpy(filebuf,filexp(fname_noexpand)); /* make sure directory exists... */ if (makedir(filebuf,MD_FILE) == 0) { (void)edit_file(fname_noexpand); #ifdef SCOREFILE_CACHE sf_file_clear(); #endif } else printf("Can't make %s\n",filebuf) FLUSH; } /* returns file number */ /* if file number is negative, the file does not exist or cannot be opened */ #ifdef SCOREFILE_CACHE static int sf_open_file(name) char* name; { FILE* fp; char* temp_name; char* s; int i; if (!name || !*name) return 0; /* unable to open */ for (i = 0; i < sf_num_files; i++) if (strEQ(sf_files[i].fname,name)) { if (sf_files[i].num_lines < 0) /* nonexistent */ return -1; /* no such file */ sf_files[i].line_on = 0; return i; } sf_num_files++; sf_files = (SF_FILE*)saferealloc((char*)sf_files, sf_num_files * sizeof (SF_FILE)); sf_files[i].fname = savestr(name); sf_files[i].num_lines = 0; sf_files[i].num_alloc = 0; sf_files[i].line_on = 0; sf_files[i].lines = NULL; temp_name = NULL; if (strncaseEQ(name,"URL:",4)) { #ifdef USEURL char lbuf[1024]; safecpy(lbuf,name,sizeof lbuf - 4); name = lbuf; temp_name = temp_filename(); if (!url_get(name+4,temp_name)) name = NULL; else name = temp_name; #else printf("\nThis copy of strn does not have URL support.\n") FLUSH; name = NULL; #endif } if (!name) { sf_files[i].num_lines = -1; return -1; } fp = fopen(name,"r"); if (!fp) { sf_files[i].num_lines = -1; return -1; } while ((s = fgets(sf_buf,LBUFLEN-4,fp)) != NULL) { if (sf_files[i].num_lines >= sf_files[i].num_alloc) { sf_files[i].num_alloc += 100; sf_files[i].lines = (char**)saferealloc((char*)sf_files[i].lines, sf_files[i].num_alloc*sizeof(char**)); } /* CAA: I kind of like the next line in a twisted sort of way. */ sf_files[i].lines[sf_files[i].num_lines++] = mp_savestr(s,MP_SCORE2); } fclose(fp); if (temp_name) UNLINK(temp_name); return i; } #endif /* SCOREFILE_CACHE */ #ifdef SCOREFILE_CACHE static void sf_file_clear() { int i; for (i = 0; i < sf_num_files; i++) { if (sf_files[i].fname) free(sf_files[i].fname); if (sf_files[i].num_lines > 0) { /* memory pool takes care of freeing line contents */ free(sf_files[i].lines); } } mp_free(MP_SCORE2); if (sf_files) free(sf_files); sf_files = (SF_FILE*)NULL; sf_num_files = 0; } #endif /* SCOREFILE_CACHE */ #ifdef SCOREFILE_CACHE static char* sf_file_getline(fnum) int fnum; { if (fnum < 0 || fnum >= sf_num_files) return NULL; if (sf_files[fnum].line_on >= sf_files[fnum].num_lines) return NULL; /* past end of file, or empty file */ /* below: one of the more twisted lines of my career (:-) */ return sf_files[fnum].lines[sf_files[fnum].line_on++]; } #endif /* SCOREFILE_CACHE */ #endif /* SCORE */