/*
 * Copyright (c) 2004	Greg A. Woods <woods@planix.com>
 */

#ident "@(#)librfc2822: :gethfield.c,v 1.3 2004/08/13 23:32:44 woods Exp "

#include <sys/cdefs.h>

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "rfc2822.h"

static char *rfc2822_read_hfield __P((FILE *, int));

/*
 * rfc2822_gethfield() -- read the next header field from the file stream on 'fp'
 *
 * Returns a pointer to the next header field found in the given file stream if
 * one is indeed found, NULL otherwise.
 *
 * The (char *) address pointed to by "colonp" is set to point to the first
 * colon in the header, i.e. the end of the header name, if the header is
 * valid.  This address will be set to point to NULL if the line is not a
 * proper RFC 2822 header line (eg. a Unix mailbox "From " line).
 *
 * If "keepwhite" is non-zero then internal formatting whitespace will be
 * preserved.
 */
char *
rfc2822_gethfield(fp, colonp, keepwhite)
	FILE *fp;
	char **colonp;
	int keepwhite;
{
	char *header;
	char *cp;

	assert(colonp);
	*colonp = NULL;

	if ((header = rfc2822_read_hfield(fp, keepwhite))) {
		/*
		 * search for the end of the first "word"
		 *
		 * (i.e. skip what is essentially the first RFC 822 atom)
		 */
		cp = header;
		while (isprint(*cp) && *cp != ' ' && *cp != '\t' && *cp != ':') {
			cp++;
		}
		if (*cp == ':') {
			*colonp = cp;
		}
	}

	return header;
}

/*
 * rfc2822_read_hfield() -- read the next header field from the file stream on 'fp'
 * 
 * Keeps state and returns NULL when there are no more headers to read.
 *
 * Complications are because this routine must deal with wrapped continuation
 * lines & other such RFC-2822 niceties.
 *
 * Returns a pointer to allocated storage containing the next header field
 * found in the given file stream if something is found, NULL otherwise.
 *
 * XXX maybe this could/should be a public function?
 */
static char *
rfc2822_read_hfield(fp, keepwhite)
	FILE *fp;
	int keepwhite;
{
	char *header;
	char *buf;
	size_t len;
	char *endp;

	if (rfc2822_get_done_headers()) {
		rfc2822_errno = RFC2822_OK;
		return NULL;
	}
	if (!(buf = fgetln(fp, &len))) {
		if (feof(fp))
			rfc2822_errno = RFC2822_STDIO_EOF;
		else if (ferror(fp))
			rfc2822_errno = RFC2822_STDIO_ERROR;
		else
			assert(1 == 0);	/* This state should be impossible... */
		return NULL;
	}
	/* a blank line signifies the end of the headers */
	if (*buf == '\n') {
		rfc2822_set_done_headers();
		rfc2822_errno = RFC2822_OK;
		return NULL;
	}
	if (! keepwhite) {
		while (len && (buf[len - 1] == ' ' || buf[len - 1] == '\t' || buf[len - 1] == '\n')) {
			len--;		/* trim off all trailing whitespace */
		}
	}
	if (!(header = (char *) calloc(len + 1, sizeof(char)))) {
		rfc2822_errno = RFC2822_FATAL_ERROR;
		return NULL;
	}
	/* the string is NUL-terminated by virtue of calloc() */
	memcpy(header, buf, len);
	/*
	 * in case there are no continuation lines we must point just past the
	 * end of the line where we'll be dropping the NUL....
	 */
	endp = header + len;
	/*
	 * Now we gather any wrap-around continuation lines....
	 */
	for (;;) {
		int c;
		char *nh;
		size_t nhlen;
		
		if ((c = fgetc(fp)) == -1) {
			if (feof(fp)) {
				rfc2822_errno = RFC2822_STDIO_EOF;
				return header;
			} else if (ferror(fp))
				rfc2822_errno = RFC2822_STDIO_ERROR;
			else
				assert(1 == 0);	/* This state should be impossible... */
			free((void *) header);
			return NULL;
		}
		if (ungetc(c, fp) == EOF) {
			rfc2822_errno = RFC2822_STDIO_ERROR;
			free((void *) header);
			return NULL;
		}
		if (c != ' ' && c != '\t') {
			/* next line not a continuation of the previous */
			break;
		}
		if (!(buf = fgetln(fp, &nhlen))) {
			if (feof(fp)) {
				rfc2822_errno = RFC2822_STDIO_EOF;
				return header;
			} else if (ferror(fp))
				rfc2822_errno = RFC2822_STDIO_ERROR;
			else
				assert(1 == 0);	/* This state should be impossible... */
			free((void *) header);
			return NULL;
		}
		if (! keepwhite) {	/* trim off any trailing whitespace */
			while (nhlen && (buf[nhlen - 1] == ' ' || buf[nhlen - 1] == '\t' || buf[nhlen - 1] == '\n')) {
				nhlen--;
			}
		}
		if (! keepwhite) {	/* skip leading whitespace */
			while (*buf == ' ' || *buf == '\t') {
				buf++;
				nhlen--;
			}
		}
		if (!(nh = (char *) realloc((void *) header, len + nhlen + (keepwhite ? 1 : 2)))) {
			int oerror = errno;

			free((void *) header);
			rfc2822_errno = RFC2822_FATAL_ERROR;
			errno = oerror;
			return NULL;
		}
		header = nh;
		endp = header + len;	/* must reset 'endp' after the realloc() */
		assert(*endp == '\0');
		if (! keepwhite) {
			*endp++ = ' ';	/* join the lines with a space */
			len++;
		}
		memcpy(endp, buf, nhlen); /* append next line */
		endp += nhlen;		/* point again at the NUL location */
		len += nhlen;
		*endp = '\0';		/* stringify the result */
	}

	return header;
}

static int done_headers = 0;

/*
 * XXX doesn't really need to be public?
 */
int
rfc2822_get_done_headers()
{
	return done_headers;
}

/*
 * XXX doesn't really need to be public?
 */
void
rfc2822_set_done_headers()
{
	done_headers = 1;
}

/*
 * This function must be called whenever a new message is started
 *
 * XXX there should be an rfc2822_begin_msg() or similar that calls this.
 */
void
rfc2822_clear_done_headers()
{
	done_headers = 0;
}
