/*
 * apply filter file to all files in a newsgroup
 *
 * Written by Cornelius Krasel <krasel@wpxx02.toxi.uni-wuerzburg.de>.
 * Copyright 1999.
 *
 * Modified and copyright of the modifications 2002 by Matthias Andree
 * <matthias.andree@gmx.de> and Ralf Wildenhues <ralf.wildenhues@gmx.de>
 *
 * See file COPYING for restrictions on the use of this software.
 */

#include "leafnode.h"
#include "ln_log.h"

#include <sys/stat.h>
#include <sys/types.h>
#include <ctype.h>
#include "system.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <utime.h>

#define MAXHEADERSIZE 2047

int debug = 0;
int verbose;

/* read from file f into malloced buffer *bufp of size *size
 * up to delim or EOF.  Buffer is adjusted to fit input.
 * return pointer to match or end of file, NULL in case of error
 */
static /*@null@*/ /*@dependent@*/ char *
readtodelim(FILE *f, const char *name, /*@unique@*/ /*@observer@*/ const char *delim,
    char **bufp, size_t *size)
{
    size_t dlen = strlen(delim) - 1;
    size_t nread, res;
    char *k;

    nread = 0;
    if (*size < 1 || *bufp == NULL)
	*bufp = critmalloc((*size = MAXHEADERSIZE), "readtodelim");

    /*@+loopexec@*/
    for (;;) {
	res = fread(*bufp + nread, 1, *size - nread - 1, f);
	(*bufp)[nread + res] = '\0';
	/* skip as much as possible */
	k = strstr(nread > dlen ? *bufp + nread - dlen : *bufp, delim);
	if (ferror(f)) {
	    printf("error reading %s\n", name);
	    clearerr(f);
	    return k;
	}
	nread += res;
	if (feof(f)) {
	    clearerr(f);
	    return k != NULL ? k : *bufp + nread;
	}
	if (k != NULL) {
	    return k;
	}
	/* must read more */
	*bufp = critrealloc(*bufp, (*size)*=2, "readtodelim");
    }
    /*@=loopexec@*/
}

/** unfold a header string \a t in-place.
 *  CRLF are converted to LF. */
static void unfold(char *t)
{
    char *i;

    for (i = t; *i ; i++) {
	if (i[0] == '\r' && i[1] == '\n')
	    continue;
	if (i[0] == '\n'
		&& (i[1] == ' ' || i[1] == '\t'))
	    continue;
	*t = *i;
	t++;
    }
    *t = '\0';
}

/* read article headers, cut off body
 * return 0 for success, -1 for error, -2 for article without body
 */
static int
readheaders(FILE *f, /*@unique@*/ const char *name, char **bufp, size_t *size)
{
    char *k = readtodelim(f, name, "\n\n", bufp, size);
    if (k != NULL) {
	/* unfold lines */
	unfold(*bufp);
	if (*k == '\0')
	    return -2;
	else {
	    k[1] = '\0';
	    return 0;
	}
    } else {
	return -1;
    }
}

int
main(int argc, char *argv[])
{
    const char c[] = "-\\|/";
    int i, score, option, deleted, kept;
    unsigned long n;
    char *msgid;
    char *l;
    size_t lsize;
    const char *msgidpath = "";
    FILE *f;
    DIR *d;
    struct dirent *de;
    struct stat st;
    struct utimbuf u;
    struct newsgroup *g;

    myopenlog("applyfilter");

    if (!initvars(argv[0]))
	exit(1);

    while ((option = getopt(argc, argv, "v")) != -1) {
	if (option == 'v')
	    verbose++;
	else {
	    printf("Usage: %s newsgroup\n", argv[0]);
	    exit(1);
	}
    }

    if (argv[optind] == NULL) {
	printf("Usage: %s newsgroup\n", argv[0]);
	exit(1);
    }

    if (!readconfig(0)) {
	printf("Reading configuration failed, exiting "
	       "(see syslog for more information).\n");
	exit(2);
    }
    freeservers();

    if (filterfile)
	readfilter(filterfile);
    else {
	printf("Nothing to filter -- no filterfile found.\n");
	freeconfig();
	exit(0);
    }

    if (try_lock(timeout_lock)) {
	printf("Cannot obtain lock file, aborting.\n");
	exit(1);
    }

    readactive();
    if (!active) {
	printf("Problem reading active file.\n");
	freeconfig();
	exit(1);
    }

    g = findgroup(argv[optind]);
    if (!g) {
	printf("Newsgroups %s not found in active file.\n", argv[optind]);
	unlink(lockfile);
	exit(1);
    }

    /* to automatically rise the low water mark, we reset g->first to
     * ULONG_MAX. */
    g->first = ULONG_MAX;
    /* We used to do g->last = 0; but that can severely confuse news
     * readers, we don't ever want to decrease the high water mark. */
    if (!chdirgroup(g->name, FALSE)) {
	printf("No such newsgroup: %s\n", g->name);
	unlink(lockfile);
	exit(1);
    }
    if (!(d = opendir("."))) {
	printf("Unable to open directory for newsgroup %s\n", g->name);
	unlink(lockfile);
	exit(1);
    }

    i = 0;
    deleted = 0;
    kept = 0;
    lsize = MAXHEADERSIZE + 1;
    l = critmalloc(lsize, "Space for article");
    while ((de = readdir(d)) != NULL) {
	if (!isdigit((unsigned char)de->d_name[0])) {
	    /* no need to stat file */
	    continue;
	}
	switch (verbose) {
	case 1:{
		printf("%c\b", c[i % 4]);
		fflush(stdout);
		i++;
		break;
	    }
	case 2:{
		printf("%s\n", de->d_name);
	    }
	}
	if ((f = fopen(de->d_name, "r")) != NULL
		&& fstat(fileno(f), &st) == 0
		&& S_ISREG(st.st_mode))
	{
	    switch (readheaders(f, de->d_name, &l, &lsize)) {
		case 0:
		case -2:
		    score = dofilter(l);
		    break;
		case -1:
		    score = -1; /* FIXME: how to handle read error? */
		    break;
		default:
		    /* this branch will never be executed, but
		     * eliminates a compiler warning */
		    score = 0;
		    break;
	    }

	    if (score) {
		msgid = fgetheader(f, "Message-ID:");
		fclose(f);
		unlink(de->d_name);
		/* delete stuff in message.id directory as well */
		if (msgid) {
		    msgidpath = lookup(msgid);
		    if ((stat(msgidpath, &st) == 0) /* RATS: ignore */
				    && (st.st_nlink < 2)) {
			if (unlink(msgidpath) == 0)
			    deleted++;
		    }
		    free(msgid);
		}
		if (verbose)
		    printf("%s %s deleted\n", de->d_name, msgidpath);
	    } else {
		fclose(f);
		n = strtoul(de->d_name, NULL, 10);
		if (n) {
		    if (n < g->first)
			g->first = n;
		    if (n > g->last)
			g->last = n;
		}
		u.actime = st.st_atime;
		u.modtime = st.st_mtime;
		utime(de->d_name, &u);
		kept++;
	    }
	} else {
	    if (f) {
		ln_log(LNLOG_SERR, LNLOG_CARTICLE, 
			"could not stat %s or not a regular file\n",
			de->d_name);
		(void)fclose(f);
	    } else {
		ln_log(LNLOG_SERR, LNLOG_CARTICLE, "could not open %s\n",
			de->d_name);
	    }
	}
    }
    closedir(d);
    free(l);
    if (g->first > g->last) {
	/* group is empty */
	g->first = g->last + 1;
    }
    if (writeactive()) ln_log(LNLOG_SERR, LNLOG_CTOP, "Error writing groupinfo.");
    freeactive(active);
    unlink(lockfile);
    printf("%d article%s deleted, %d kept.\n", deleted, PLURAL(deleted), kept);

    if (verbose)
	printf("Updating .overview file\n");
    getxover();
    freexover();
    freeconfig();
    exit(0);
}


syntax highlighted by Code2HTML, v. 0.9.1