#ifndef lint
static char sccsid[] = "@(#)npr.c	1.2.1.2 (Mikros) 88/08/24";
#endif

/*
 * npr - news pretty printer
 *
 * This program is similar to the mail pretty printer, which
 * recently flows on the net. It will pretty print news articles
 * on a PostScript laser-printer. 77% of this software has been
 * published with other software (so as the header functions
 * comes from the news software distribution), 22% are my own
 * work and 1% may be Bugs :-)
 *
 * Written (w) 1988 by Stefan Stapelberg, stefan@mikros.uucp
 *
 ***************************************************************
 *
 * SCCS CONTROL BLOCK
 * MODULE:       npr.c
 * TYPE:         
 * VERSION:      1.2.1.2
 * LAST CHANGED: 18:10:59 88/08/24
 * RETRIEVED:    18:36:02 88/08/24
 *
 ***************************************************************
 *
 * Modification History
 *
 *	88/08/18	<stefan@mikros>
 *		Fixed -h flag, which ignores given header title
 *
 *	88/08/18	<bernd@actisb>
 *		Fixed DocumentFont-comment, made work
 *		on 4.3BSD and SYS V R2/R3
 *
 *	88/08/24	<stefan@mikros>
 *		Misc cleanup
 *
 */

#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <pwd.h>
#include <time.h>

#ifdef BSD
#include <strings.h>
#define strchr	index
#define strrchr	rindex
#else /* !BSD */
#ifdef SYSV
#include <string.h>
#else /* !BSD && !SYSV */
extern char *strcpy(), *strncpy();
extern char *strcat(), *strncat();
extern char *strchr(), *strrchr();
extern char *malloc(), *getenv();
#endif /* !SYSV */
#endif /* !BSD */

#include "npr.h"		/* local definitions */

#define LMARG	 0.75		/* left margin at 0.75 inch */
#define RMARG	 7.75		/* right margin at 7.75 inch */
#define BMARG	 0.5		/* bottom margin at 0.5 inch */
#define TMARG	11.0		/* top margin at 11 inch */

#define LOGOS	4		/* highest logo number (starting from 0) */

#define POINT(inch)	((int)(inch * 72.0))	/* convert inches to points */

static void syserr(), prterr();

/*
 * global variables
 */

static char usage[] = "Usage:\n \
  %s [-p prolog] [-h title] [-H-field +field] [-f font] [-s size] [file ...]\n";

static char *progname = NULL;		/* program's name */
static char *username = NULL;		/* user's name */
static char *textfont = NULL;		/* default text font */
static char *headline = NULL;		/* user setable headline */
static char *profile = PRODEF;		/* file containing PS prologue */
static char *curfile = NULL;		/* current file name */
static char *artid = NULL;		/* local article number */
static char *fonts[10];			/* font definitions from prologue */

static int textsize = 10;		/* default text font size */
static int dirlookup = 1;		/* directory lookup for prologue file */
static int newline = 0;			/* accumulated newlines */
static int mailbox = 0;			/* set if mbox.saved */
static int artno = 0;			/* article sequence number */
static int fontidx = 0;			/* index into font definition list */

static double lmarg = LMARG;		/* left margin (indent) */

static int ismbox(), isnorm();		/* this are the header functions */
static int isfrom(), ishead();
static int isnewl(), other();

static void setmbox(), setnorm();	/* this are the header actions */
static void setfrom(), sethead();
static void prtext(), prhead();

extern char *fgets(), *hfgets();

enum STATE { CHECK, HEADER, TEXT };

static struct {
	enum STATE oldstate;		/* state */
	int (*cond)();			/* condition */
	void (*fct)();			/* action */
	enum STATE newstate;		/* new state */
	char *(*nextline)();		/* get next line */
} automat[] = {
/* old state	condition	action		newstate	nextline */
 { CHECK,	ismbox,		setmbox,	CHECK,		fgets  },
 { CHECK,	isnorm,		setnorm,	CHECK,		fgets  },
 { CHECK,	isfrom,		setfrom,	HEADER,		hfgets },
 { CHECK,	isnewl,		prtext,		CHECK,		fgets  },
 { CHECK,	other,		prtext,		TEXT,		fgets  },
 { HEADER,	ishead,		sethead,	HEADER,		hfgets },
 { HEADER,	other,		prhead,		TEXT,		fgets  },
 { TEXT,	isnewl,		prtext,		CHECK,		fgets  },
 { TEXT,	other,		prtext,		TEXT,		fgets  }
};

static struct header headtab[] = {
 { "Path: ",		NULL,	MIXED },
 { "From: ",		NULL,	MIXED },
 { "Newsgroups: ",	NULL,	MIXED },
 { "Subject: ",		NULL,	MIXED },
 { "Date: ",		NULL,	MIXED },
 { "Date-Received: ",	NULL,	MIXED },
 { "Received: ",	NULL,	MIXED },
 { "Expires: ",		NULL,	MIXED },
 { "Article-I.D.: ",	NULL,	MIXED },
 { "Message-ID: ",	NULL,	MIXED },
 { "Reply-To: ",	NULL,	MIXED },
 { "References: ",	NULL,	MIXED },
 { "Control: ",		NULL,	MIXED },
 { "Sender: ",		NULL,	MIXED },
 { "Followup-To: ",	NULL,	MIXED },
 { "Distribution: ",	NULL,	MIXED },
 { "Organization: ",	NULL,	MIXED },
 { "Lines: ",		NULL,	MIXED },
 { "Summary: ",		NULL,	MIXED },
 { "Keywoards: ",	NULL,	MIXED },
 { "Approved: ",	NULL,	MIXED },
 { "Nf-ID: ",		NULL,	HIDE  },
 { "Nf-From: ",		NULL,	HIDE  },
 { "Supersedes: ",	NULL,	HIDE  },
 { "Xref: ",		NULL,	HIDE  },
 { "Xpath: ",		NULL,	HIDE  },
 { "Posting-Version: ",	NULL,	HIDE  },
 { "Relay-Version: ",	NULL,	HIDE  }
};



main(argc, argv)
int argc;
char *argv[];
{
	extern char *getenv();
	extern void srand();
	int setopt(), vstrip();
	int prologue();
	void printnews();

	char *ep = NULL;		/* ptr to environment variable */
	char *optv[50];			/* array of ptrs to options */
	int optc;			/* no of broken down fields in optv */
	int fc = 0;			/* no of cmd line args */
	int rc = 0;			/* contains exit code */
	FILE *fp, *fopen();


	progname = argv[0];		/* save program name */
	srand(getpid());		/* initialize random generator */

	/* get options from environment variable */
	if ((ep = getenv("NPROPT")) != NULL) {
		optv[0] = progname;
		optc = vstrip(ep, optv+1, TABSIZE(optv)-1);
		if (optc++ > 0 && setopt(optc, optv) == 0)
			rc++;
	}

	/* get options from command line */
	if ((fc = setopt(argc, argv)) == 0)
		rc++;

	if (rc) {
		fprintf(stderr, usage, progname);
		exit(rc);
	}

	/* print prologue file */
	if (rc = prologue())
		exit(rc);

	/* finally print news */
	if (fc == argc) {
		curfile = "stdin";
		printnews(stdin);
	} else for (argv += fc; *argv != NULL; argv++) {
		if ((fp = fopen(*argv, "r")) == NULL) {
			syserr("Can't open `%s'", *argv);
			rc++;
			continue;
		}
		curfile = *argv;
		printnews(fp);
		fclose(fp);
	}
	printf("%%%%Trailer\n");
	printf("%%%%DocumentFonts:");
	while (fontidx && fonts[--fontidx] != NULL)
		printf(" %s", fonts[fontidx]);
	putchar('\n');

	exit(rc);
}


static int prologue()
{
	extern char *getlogin();
	char *strsave();
	char *strfind();
	char *strback();
	char *expand();

	char lbuf[LBUFLEN];			/* line buffer */
	char tbuf[BUFLEN];			/* temp buffer */
	char *p, *p1;				/* pointer into buffer */

	FILE *fp, *fopen();			/* file ptr prologue file */
	struct passwd *pw, *getpwuid();		/* user's real name */

	username = getlogin();		/* get user's name */
	if (username == NULL || *username == '\0') {
		if ((pw = getpwuid(getuid())) == NULL) {
			prterr("Cannot get user's name");
			return 1;
		}
		username = pw->pw_name;
	}

	/* open prologue file, print it */
	if ((fp = fopen(profile, "r")) == NULL && dirlookup && *profile != '/') {
		sprintf(tbuf, "%s/%s", PRODIR, profile);
		fp = fopen(tbuf, "r");
	}

	if (fp == NULL) {
		prterr("Can't open prologue file `%s'", profile);
		return 1;
	}

	printf("%%!PS-Adobe-1.0\n");
	printf("%%%%Title: USENET News\n");
	printf("%%%%Creator: npr by Stefan Stapelberg\n");
	printf("%%%%CreationDate: %s\n", sccsid);
	printf("%%%%BoundingBox: %d %d %d %d\n",
		POINT(lmarg), POINT(BMARG), POINT(RMARG), POINT(TMARG));
	printf("%%%%For: %s\n", username);
	printf("%%%%DocumentFonts: (atend)\n");
	printf("%%%%Pages: (atend)\n");
	printf("%%%%EndComments\n\n");

	while (fgets(lbuf, sizeof lbuf, fp) != NULL) {
		if ((p=strfind(lbuf,"findfont")) && (p=strback(lbuf,p,'/')) &&
		    (p1=strchr(p,' '))) {
			strncpy(tbuf,p+1,p1-p-1);
			tbuf[p1-p-1] = '\0';
			fonts[fontidx++] = strsave(tbuf);
		}
		fputs(lbuf, stdout);
	}
	fclose(fp);

	printf("/LM %3.3f inch def\t%% left margin\n", lmarg);
	printf("/RM %3.3f inch def\t%% right margin\n", RMARG);
	printf("/TM %3.3f inch def\t%% top margin\n", TMARG);
	printf("/BM %3.3f inch def\t%% bottom margin\n\n", BMARG);

	if (textfont != NULL) {
		printf("/Textfont /%s findfont %d scalefont bind def\n", textfont, textsize);
		printf("/nldist %d def\t%% text nl distance\n", textsize+1);
	}

	printf("%%%%EndProlog\n\n");

	return 0;
}


static void printnews(fp)
FILE *fp;
{
	extern char *hfgets(), *expand();

	char lbuf[LBUFLEN];		/* line buffer */
	enum STATE state = CHECK;	/* current state */
	int i;


	if (hfgets(lbuf, sizeof lbuf, fp) == NULL) {
		prterr("Premature EOF on %s", curfile);
		return;
	}

	do {
		for (i=0; i < TABSIZE(automat); i++)
			if (state == automat[i].oldstate &&
			    (*automat[i].cond)(lbuf)) {
				(*automat[i].fct)(lbuf);
				state = automat[i].newstate;
				break;
			}
		if (i == TABSIZE(automat)) {
			prterr("Error in automaton - state %d", state);
			return;
		}
	} while ((*automat[i].nextline)(lbuf, sizeof lbuf, fp) != NULL);

	if (artno > 0)
		printf("\nEndText\n");
	else
		prterr("No articles found in %s\n", curfile);

	return;
}


/*
 * ismbox - check for mailbox saved style.
 *	This is indicated by a line like
 *		"From user date",
 *	so we just look for "From "
 */

static int ismbox(p)
char *p;
{
	return(isalpha(*p) && PREFIX(p, "From "));
}

/*
 * isnorm - check for norm saved style.
 *	This is indicated by a line like
 *		"Article num of news.group:"
 *	Lets look for the colon terminating the
 *	line, and the prefix "Article "
 */

static int isnorm(p)
char *p;
{
	char *colon = strchr(p, ':');
	return(colon && *(colon+1) == '\n' && PREFIX(p, "Article "));
}

/*
 * isfrom - check for interactive saved style.
 *	Lines are beginning with "From: " or "Path: "
 *	We must take into account, that mbox.saved news
 *	has a ">From: " line rather than "From: "
 */

static int isfrom(p)
char *p;
{
	if (!ishead(p))
		return 0;
	
	if (mailbox && *p == '>')
		p++;

	if (PREFIX(p, "From: ") || PREFIX(p, "Path: "))
		return 1;

	return 0;
}

/*
 * ishead - check for header line. These are lines with
 *	a colon followed by a space and an optional text.
 */

static int ishead(p)
char *p;
{
	char *colon = strchr(p, ':');
	char *space = strchr(p, ' ');

	return(colon && space && colon < space);
}

/*
 * isnewl - check for line containing only a newline character.
 */

static int isnewl(p)
char *p;
{
	return(*p == '\n');
}

/*
 * other - dummy function always returning TRUE
 */

static int other(p)	/* ARGSUSED */
char *p;
{
	return 1;
}

/*
 * setmbox - set mailbox variable to indicate stupid
 *	mbox.saved style.  We later have to look for
 *	a ">From: " header rather than just "From: "
 */

static void setmbox(p)	/* ARGSUSED */
char *p;
{
	mailbox = 1;
	return;
}

/*
 * setnorm - save article number for later usage.
 */

static void setnorm(p)	/* ARGSUSED */
char *p;
{
	static char ident[BUFLEN];

	(void) nstrip(p);
	ident[0] = '\0';
	artid = strncpy(ident, p, sizeof ident-1);
	return;
}

/*
 * setfrom - initialize header table.
 *	Insert from/path header.
 */

static void setfrom(p)	/* ARGSUSED */
char *p;
{
	void sethead();

	int i;

	/* clear table */
	for (i=0; i < TABSIZE(headtab); i++) {
		if (headtab[i].h_string != NULL) {
			(void) free(headtab[i].h_string);
			headtab[i].h_string = NULL;
		}
	}

	sethead(p);
	return;
}

/*
 * sethead - insert header into header table.
 */

static void sethead(p)	/* ARGSUSED */
char *p;
{
	char *getfield();
	int i, type();

	if (mailbox && *p == '>')
		p++;

	if ((i = type(p)) < 0)		/* unknown headers are thrown away */
		return;
	
	if (headtab[i].h_string == NULL)
		headtab[i].h_string = getfield(p);
	return;
}

/*
 * prhead - print collected header, if the entry
 *	collected so far is valid
 */

static void prhead(p)	/* ARGSUSED */
char *p;
{
	extern char *ctime();
	extern int rand();
	extern long time();			/* know C :-) */

	char *expand();
	int isfield(), type();

	char tbuf[BUFLEN];
	int i;
	long curtim;


	if (!(isfield("From: ") || isfield("Path: ")) ||
	     !isfield("Date: ") || !isfield("Message-ID: ")) {
		prterr("Invalid header lines in %s", curfile);
		return;
	}
	
	if (++artno > 1)
		printf("\nEndText\n\n");

	printf("/PrintLogo /Logo%d def\n", rand() % LOGOS);

	printf("/Headline ");
	if (headline != NULL) {
		printf("(%s) ", expand(headline));
	} else if (artid != NULL) {
		printf("(%s) ", expand(artid));
		artid = NULL;
	} else {
		printf("(Article %d from %s) ", artno, curfile);
	}
	printf("def\n");

	if ((i = type("Subject: ")) >= 0 && headtab[i].h_string != NULL) {
		printf("/Subline (%s) def\n", expand(headtab[i].h_string));
	} else {
		curtim = time((long *)0);
		(void) sprintf(tbuf, "Printed for %s, %s",
		     username, ctime(&curtim));
		(void) nstrip(tbuf);
		printf("/Subline (%s) def\n", expand(tbuf));
	}

	/* print header of article */
	printf("/HeaderList [\t\t%% begin array:\n");

	for (i=0; i < TABSIZE(headtab); i++)
		if (headtab[i].h_display && headtab[i].h_string) {
			printf("   [ (%s) ", expand(headtab[i].h_prefix));
			printf(" (%s) ]\n", expand(headtab[i].h_string));
		}

	printf(" ] def\n");

	printf("%%%%Page 1 1\n");
	printf("1 InitPage PrintHeader\n");

	return;
}

/*
 * prtext - print ordinary text line, but
 *	only if we are inside of an article
 */

static void prtext(p)	/* ARGSUSED */
char *p;
{
	void tflush();
	char *expand();

	if (artno == 0)		/* no current article */
		return;
	
	if (*p != '\n') {
		tflush();
		(void) nstrip(p);
		printf("(%s)\n   PrintText  ", expand(p));
	}
	newline++;

	return;
}


static int setopt(ac, av)
int ac;
char *av[];
{
	extern char *optarg;
	extern int optind;
	extern int getopt();
	extern double atof();
	void headign();

	int c, rc = 0;

	optind = 1;
	while ((c = getopt(ac, av, "h:H:i:f:s:p:P:")) != EOF) {
		switch (c) {
		   case 'P':		/* look for file in PRODIR also */
		   case 'p':		/* look for file in current dir only */
			dirlookup = isupper(c) ? 1 : 0;
			if ((profile = optarg) == NULL || *profile == '\0')
				rc++;
			break;
		   case 'i':
			if ((lmarg = atof(optarg)) < 0.25 || lmarg > 5.0)
				lmarg = LMARG;
			break;
		   case 'f':
			textfont = optarg;
			if (*textfont == '/')
				textfont++;
			break;
		   case 'h':
			headline = optarg;
			break;
		   case 'H':
			headign(optarg);
			break;
		   case 's':
			if ((textsize = atoi(optarg)) < 1 || textsize > 20)
				textsize = 10;
			break;
		   default:
			rc++;
			break;
		}
	}
	return(rc ? 0 : optind);
}

static void headign(l)
char *l;
{
	char tmpbuf[NAMELEN];
	char *p = l;
	int i, type();

	do {
		while (*p != '\0' && *p != ' ' && *p != '\t')
			p++;
		
		if (*p != '\0')
			*p++ = '\0';
		
		if (p-l > 2) {
			(void) sprintf(tmpbuf, "%s ", l+1);
			if ((i = type(tmpbuf)) >= 0) {
				switch (*l) {
				  case '-':	headtab[i].h_display = HIDE;
						break;
				  case '+':	headtab[i].h_display = MIXED;
						break;
				  case '*':	headtab[i].h_display = BOLD;
						break;
				}
			}
		}
		l = p;
	} while (*l != '\0');

	return;
}


static void tflush()
{
	if (newline) {
		printf("%d Newline\n", newline);
		newline = 0;
	}
	return;
}


#define SPACEFOR(count)	((tp - tmpbuf) < (sizeof(tmpbuf) - count))
#define TABSTOP		8

static char *expand(s)
char *s;
{
	static char tmpbuf[LBUFLEN];
	char c, *tp = tmpbuf;
	int overflow = 0;
	int n, col = 0;

	if (s == NULL) {
		strcpy(tmpbuf, "(null)");
		return((char *)tmpbuf);
	}

	while (!overflow && (c = *s++) != '\0') {
		switch (c) {
		   case '\\':
		   case '(':
		   case ')':
			if (SPACEFOR(2)) {
				*tp++ = '\\';
				*tp++ = c;
				col++;
			} else
				overflow++;
			break;
		   case '\t':
			n = (col > 0) ? TABSTOP - (col % TABSTOP) : TABSTOP;
			if (SPACEFOR(n)) {
				while (n-- > 0) {
					*tp++ = ' ';
					col++;
				}
			} else
				overflow++;
			break;
		    case '\n':
		    case '\b':
		    case '\f':
		    case '\r':
			if ((col = (c == '\b') ? col-1 : 0) < 0)
				col = 0;
			if (SPACEFOR(2)) {
				*tp++ = '^';
				*tp++ = c + '@';
			}
			break;
		    default:
			if (SPACEFOR(1)) {
				*tp++ = c;
				col++;
			} else
				overflow++;
			break;
		}
	}
	*tp = '\0';

	return (char *)tmpbuf;
}

static int vstrip(from, av, len)
char *from, *av[];
int len;
{
	static char lbuf[LBUFLEN];
	char c, quote = '\0';
	char *to = lbuf;
	int inword = 0;
	int i = 0;

	av[i] = lbuf;

	while ((c = *from++) != '\0'
	    && i < (len - 1) && (to - lbuf) < (sizeof lbuf - 1)) {
		switch (c) {
		   case '"':
		   case '\'':
		   case '\\':
			if (quote != '\\' && (quote == '\0' || quote == c)) {
				quote = (quote == '\0' ? c : '\0');
				break;
			}
		   case ' ':
		   case '\t':
			if (quote == '\0') {
				if (inword) {
					*to++ = '\0';
					av[++i] = to;
					inword = 0;
				}
				break;
			}
		   default:
			*to++ = c;
			inword = 1;
			if (quote == '\\')
				quote = '\0';
			break;
		}
	}

	if (inword) {
		*to = '\0';
		i++;
	}
	av[i] = NULL;

	return i;
}


/*
 * syserr --	prints system error messages.
 *		Assumes that command name has been
 *		assigned to variable `progname'.
 */

static void syserr(m1, m2, m3)	/*VARARGS*/
char *m1, *m2, *m3;
{
	extern int errno, sys_nerr;
	extern char *sys_errlist[];

	if (progname)
		fprintf(stderr, "%s: ", progname);

	fprintf(stderr, m1, m2, m3);

	fprintf(stderr, " (%d", errno);		/* generate error message */
	if(errno > 0 && errno < sys_nerr)
		fprintf(stderr, ": %s)", sys_errlist[errno]);
	else
		fputc(')', stderr);

	fputc('\n', stderr);
	return;
}

/*
 * prterr --	prints error messages.
 *	Assumes that command name has been
 *	assigned to variable `progname'.
 */

static void prterr(m1, m2, m3)	/*VARARGS*/
char *m1, *m2, *m3;
{
	if (progname)
		fprintf(stderr, "%s: ", progname);

	fprintf(stderr, m1, m2, m3);
	fputc('\n', stderr);

	return;
}


static int isfield(field)
char *field;
{
	int i = type(field);
	return (i < 0) || (headtab[i].h_string == NULL) ? FALSE : TRUE;
}

static char *strsave(str)
char *str;
{
	extern char *malloc();
	char *p = malloc(strlen(str) + 1);

	return (p == NULL) ? p : strcpy(p, str);
}

static char *strfind(s1, s2)
char *s1, *s2;
{
	int l1 = strlen(s1);
	int l2 = strlen(s2);

	while (l1 >= l2 && strncmp(s1, s2, l2)) {
		l1--;
		s1++;
	}
	return l1 >= l2 ? s1 : NULL;
}

static char *strback(s, s1, c)
char *s, *s1;
char c;
{
	while (s1 > s && *s1 != c)
		s1--;
	return *s1 == c ? s1 : NULL;
}


/*
 * Get the given field of a header (char * parm) from bfr,
 * but only if there's something actually there (after the colon).
 */

static char *getfield(bp)
char *bp;
{
	char *strsave();
	char *p = strchr(bp, ':');

	if (p == NULL || *++p != ' ')
		return NULL;

	for (++p; isspace(*p); p++)
		;
	
	if (*p == '\0')
		return NULL;
	
	(void) nstrip(p);
	return strsave(p);
}


static int type(p)
char *p;
{
	char *colon, *space;
	int i;

	if (p == NULL || *p == '\0')
		return -1;

	colon = strchr(p, ':');
	space = strchr(p, ' ');

	if (!colon || space && space < colon)
		return -1;

	for (i = 0; i < TABSIZE(headtab); i++) {
		if (PREFIX(p, headtab[i].h_prefix))
			break;
	}

	if (i == TABSIZE(headtab))
		return -1;

	return i;
}

/*
 * hfgets is like fgets, but deals with continuation lines.
 * It also ensures that even if a line that is too long is
 * received, the remainder of the line is thrown away
 * instead of treated like a second line.
 */

static char *hfgets(buf, len, fp)
char *buf;
int len;
FILE *fp;
{
	int c;
	int n = 0;
	char *cp;

	cp = buf;
	while (n < len && (c = getc(fp)) != EOF) {
		if (c == '\n')
			break;
		if (isprint(c) || c == '\b' || c == ' ' || c == '\t') {
			*cp++ = c;
			n++;
		}
	}
	if (c == EOF && cp == buf)
		return NULL;
	*cp = '\0';

	if (c != '\n') {
		/* Line too long - part read didn't fit into a newline */
		while ((c = getc(fp)) != '\n' && c != EOF)
			;
	} else if (cp == buf) {
		/* Don't look for continuation of blank lines */
		*cp++ = '\n';
		*cp = '\0';
		return buf;
	}

	while ((c = getc(fp)) == ' ' || c == '\t') {	/* for each cont line */
		/* Continuation line. */
		while ((c = getc(fp)) == ' ' || c == '\t')
			;
		if (c == '\n')
			continue;

		if (n++ < len)
			*cp++ = ' ';
		do {
			if ((isprint(c) || c == '\b' || c == ' ' || c == '\t')
				&& n++ < len)
				*cp++ = c;
		} while ((c = getc(fp)) != '\n' && c != EOF);
	}
	if (n >= len - 1)
		cp = buf + len - 2;
	*cp++ = '\n';
	*cp = '\0';
	if (c != EOF)
		(void) ungetc(c, fp); /* push back first char of next header */
	return buf;
}


/*
 * auxiliary functions
 * most of this code comes from the network.
 */


/*
 * Strip trailing newlines, blanks, and tabs from 's'.
 * Return TRUE if newline was found, else FALSE.
 */

static int nstrip(s)
char *s;
{
	char *p;
	int rc;

	rc = FALSE;
	p = s;
	while (*p)
		if (*p++ == '\n')
			rc = TRUE;
	while (--p >= s && (*p == '\n' || *p == ' ' || *p == '\t'))
		;
	*++p = '\0';
	return rc;
}

/* From UC Berkeley @(#)strcasecmp.c	1.3 (Berkeley) 8/3/87 */

/*
 * This array is designed for mapping upper and lower case letter
 * together for a case independent comparison.  The mappings are
 * based upon ascii character sequences.
 */
char charmap[] = {
	'\000', '\001', '\002', '\003', '\004', '\005', '\006', '\007',
	'\010', '\011', '\012', '\013', '\014', '\015', '\016', '\017',
	'\020', '\021', '\022', '\023', '\024', '\025', '\026', '\027',
	'\030', '\031', '\032', '\033', '\034', '\035', '\036', '\037',
	'\040', '\041', '\042', '\043', '\044', '\045', '\046', '\047',
	'\050', '\051', '\052', '\053', '\054', '\055', '\056', '\057',
	'\060', '\061', '\062', '\063', '\064', '\065', '\066', '\067',
	'\070', '\071', '\072', '\073', '\074', '\075', '\076', '\077',
	'\100', '\141', '\142', '\143', '\144', '\145', '\146', '\147',
	'\150', '\151', '\152', '\153', '\154', '\155', '\156', '\157',
	'\160', '\161', '\162', '\163', '\164', '\165', '\166', '\167',
	'\170', '\171', '\172', '\133', '\134', '\135', '\136', '\137',
	'\140', '\141', '\142', '\143', '\144', '\145', '\146', '\147',
	'\150', '\151', '\152', '\153', '\154', '\155', '\156', '\157',
	'\160', '\161', '\162', '\163', '\164', '\165', '\166', '\167',
	'\170', '\171', '\172', '\173', '\174', '\175', '\176', '\177',
	'\200', '\201', '\202', '\203', '\204', '\205', '\206', '\207',
	'\210', '\211', '\212', '\213', '\214', '\215', '\216', '\217',
	'\220', '\221', '\222', '\223', '\224', '\225', '\226', '\227',
	'\230', '\231', '\232', '\233', '\234', '\235', '\236', '\237',
	'\240', '\241', '\242', '\243', '\244', '\245', '\246', '\247',
	'\250', '\251', '\252', '\253', '\254', '\255', '\256', '\257',
	'\260', '\261', '\262', '\263', '\264', '\265', '\266', '\267',
	'\270', '\271', '\272', '\273', '\274', '\275', '\276', '\277',
	'\300', '\341', '\342', '\343', '\344', '\345', '\346', '\347',
	'\350', '\351', '\352', '\353', '\354', '\355', '\356', '\357',
	'\360', '\361', '\362', '\363', '\364', '\365', '\366', '\367',
	'\370', '\371', '\372', '\333', '\334', '\335', '\336', '\337',
	'\340', '\341', '\342', '\343', '\344', '\345', '\346', '\347',
	'\350', '\351', '\352', '\353', '\354', '\355', '\356', '\357',
	'\360', '\361', '\362', '\363', '\364', '\365', '\366', '\367',
	'\370', '\371', '\372', '\373', '\374', '\375', '\376', '\377',
};

static int strcasecmp(s1, s2)
register char *s1, *s2;
{
	register char *cm = charmap;

	while (cm[*s1] == cm[*s2++])
		if (*s1++ == '\0')
			return 0;
	return cm[*s1] - cm[*--s2];
}

static int strncasecmp(s1, s2, n)
register char *s1, *s2;
register int n;
{
	register char *cm = charmap;

	while (--n >= 0 && cm[*s1] == cm[*s2++])
		if (*s1++ == '\0')
			return 0;
	return n < 0 ? 0 : cm[*s1] - cm[*--s2];
}

static int prefix(full, pref)
register char *full, *pref;
{
	register char *cm = charmap;

	while (*pref != '\0') {
		if (cm[*full++] != cm[*pref++])
			return FALSE;
	}
	return TRUE;
}


static void lcase(s)
register char *s;
{
	register char *ptr;

	for (ptr = s; *ptr; ptr++)
		if (isupper(*ptr))
			*ptr = tolower(*ptr);

	return;
}
