/*
** m_getfld2 -- replacement for m_getfld()
**              read an RFC 822 message
*/

#define _WITH_GETLINE
#define _POSIX_C_SOURCE 200809L

#include <h/mh.h>
#include <h/utils.h>
#include <stdio.h>
#include <ctype.h>


enum threestate {
	B_TRUE = 1,
	B_FALSE = 0,
	FAIL = -1,
};

/*
** static prototypes
*/
static enum threestate is_falted(FILE *);
static size_t copyname(char *, char *);
static boolean is_separator(char *);


/*
** FLD2:	We read a (complete) header field
** BODY2:	We read a body line
** LENERR2:	Line is too long (>998, as defined by RFC 822)
** FMTERR2:	Header field invalid
** IOERR2:	Failure to read
** FILEEOF2:	We're at the end of the file
**
** f->name is only filled in FLD2.
**
** In FLD2, f->value contains the field's (complete) value only;
** in BODY2, LENERR2 and FMTERR2 f->value contains the whole line;
** in IOERR2 and FILEEOF2 f->value is not set.
*/
enum state
m_getfld2(enum state s, struct field *f, FILE *msg)
{
	char *tmpline = NULL;
	size_t len = 0;
	ssize_t nchars;
	enum threestate falted = B_FALSE;
	enum state ret = s;

	switch (s) {
	case FLD2:
		nchars = getline(&tmpline, &len, msg);
		if (nchars < 1) {
			free(f->value);
			*f = (struct field) { "\0", 0, NULL, 0, 0 };
			if (feof(msg)) {
				return FILEEOF2;
			} else {
				return IOERR2;
			}
		}

		f->crlf = (nchars > 2 && tmpline[nchars-2] == '\r');
		if (nchars > NAMESZ+1 || (!f->crlf && nchars > NAMESZ)) {
			ret = LENERR2;
		}

		if (*(tmpline + nchars - 1) != '\n') {
			ret = FMTERR2;
		}

		if (ret == FLD2 && is_separator(tmpline)) {
			/* header/body separator found */
			free(tmpline);
			return m_getfld2(BODY2, f, msg);
		}

		f->namelen = copyname(f->name, tmpline);
		if (f->namelen < 1) {
			*f->name = '\0';
			f->namelen = 0;
			ret = FMTERR2;
		}

		/* copy the field's value */
		if (f->alloclen <= nchars - f->namelen) {
			f->value = mh_xrealloc(f->value, f->alloclen + len);
			f->alloclen += len;
		}
		if (f->namelen != 0) {
			strcpy(f->value, tmpline + f->namelen + 1);
			f->valuelen = nchars - f->namelen - 1;
		} else {
			strcpy(f->value, tmpline);
			f->valuelen = nchars;
		}

		while ((ret == FLD2 || ret == LENERR2) && (falted = is_falted(msg)) == B_TRUE) {
			nchars = getline(&tmpline, &len, msg);
			if (nchars <= 0) {
				free(tmpline);
				return IOERR2;
			}

			if (nchars > NAMESZ+1 || (!f->crlf && nchars > NAMESZ)) {
				ret = LENERR2;
			}

			if (*(tmpline + nchars - 1) != '\n') {
				ret = FMTERR2;
			}

			if (f->alloclen - f->valuelen <= nchars) {
				f->value = mh_xrealloc(f->value,
						f->alloclen + len);
				f->alloclen += len;
			}
			strcpy(f->value + f->valuelen, tmpline);
			f->valuelen += nchars;

		}

		if (falted == FAIL) {
			ret = IOERR2;
		}

		free(tmpline);
		return ret;

	case BODY2:
		*f->name = '\0';
		f->namelen = 0;

		nchars = getline(&tmpline, &len, msg);
		if (nchars < 1) {
			free(f->value);
			f->value = NULL;
			f->valuelen = 0;
			f->alloclen = 0;
			if (feof(msg)) {
				return FILEEOF2;
			} else {
				return IOERR2;
			}
		}

		f->crlf = (nchars > 2 && tmpline[nchars-2] == '\r');
		free(f->value);
		f->value = tmpline;
		f->valuelen = nchars;
		f->alloclen = len;
		return ret;

	default:
		/* give error states back as received */
		return s;
	}
}

static enum threestate
is_falted(FILE *msg)
{
	enum threestate ret;
	int c;

	if ((c = getc(msg)) < 0) {
		if (feof(msg)) {
			clearerr(msg);
			return B_FALSE;
		} else {
			return FAIL;
		}
	}
	if (isblank(c)) {
		ret = B_TRUE;
	} else {
		ret = B_FALSE;
	}
	if (ungetc(c, msg) != c) {
		return FAIL;
	}
	return ret;
}

static size_t
copyname(char *dst, char *src)
{
	size_t len;
	char *cp, *sep;

	if (!(sep = strchr(src, ':'))) {
		return 0;
	}
	/* whitespace is forbidden in name */
	for (cp=src; cp<sep; cp++) {
		if (isspace(*cp)) {
			return 0;
		}
	}

	len = sep - src;
	if (len >= NAMESZ - 1) {
		return 0;
	}

	src[len] = '\0';
	strcpy(dst, src);

	return strlen(dst);
}

static boolean
is_separator(char *line)
{
	/*
	** In MH, lines that are consists of dashes only are
	** separators as well ... not so in RFC 822.
	*/
	while (*line == '-') {
		line++;
	}
	if (strcmp("\n", line) == 0 || strcmp("\r\n", line) == 0 ) {
		return TRUE;
	}
	return FALSE;
}
