/*	$OpenBSD: rfc5322.c,v 1.2 2018/10/24 18:59:29 gilles Exp $	*/

/*
 * Copyright (c) 2018 Eric Faurot <eric@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "includes.h"

#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>

#include "rfc5322.h"

struct buf {
	char	*buf;
	size_t	 bufsz;
	size_t	 buflen;
	size_t	 bufmax;
};

static int buf_alloc(struct buf *, size_t);
static int buf_grow(struct buf *, size_t);
static int buf_cat(struct buf *, const char *);

struct rfc5322_parser {
	const char	*line;
	int		 state;		/* last parser state */
	int		 next;		/* parser needs data */
	int		 unfold;
	const char	*currhdr;
	struct buf	 hdr;
	struct buf	 val;
};

struct rfc5322_parser *
rfc5322_parser_new(void)
{
	struct rfc5322_parser *parser;

	parser = calloc(1, sizeof(*parser));
	if (parser == NULL)
		return NULL;

	rfc5322_clear(parser);
	parser->hdr.bufmax = 1024;
	parser->val.bufmax = 65536;

	return parser;
}

void
rfc5322_free(struct rfc5322_parser *parser)
{
	free(parser->hdr.buf);
	free(parser->val.buf);
	free(parser);
}

void
rfc5322_clear(struct rfc5322_parser *parser)
{
	parser->line = NULL;
	parser->state = RFC5322_NONE;
	parser->next = 0;
	parser->hdr.buflen = 0;
	parser->val.buflen = 0;
}

int
rfc5322_push(struct rfc5322_parser *parser, const char *line)
{
	if (parser->line) {
		errno = EALREADY;
		return -1;
	}

	parser->line = line;
	parser->next = 0;

	return 0;
}

int
rfc5322_unfold_header(struct rfc5322_parser *parser)
{
	if (parser->unfold) {
		errno = EALREADY;
		return -1;
	}

	if (parser->currhdr == NULL) {
		errno = EOPNOTSUPP;
		return -1;
	}

	if (buf_cat(&parser->val, parser->currhdr) == -1)
		return -1;

	parser->currhdr = NULL;
	parser->unfold = 1;

	return 0;
}

static int
_rfc5322_next(struct rfc5322_parser *parser, struct rfc5322_result *res)
{
	size_t len;
	const char *pos, *line;

	line = parser->line;

	switch(parser->state) {

	case RFC5322_HEADER_START:
	case RFC5322_HEADER_CONT:
		res->hdr = parser->hdr.buf;

		if (line && (line[0] == ' ' || line[0] == '\t')) {
			parser->line = NULL;
			parser->next = 1;
			if (parser->unfold) {
				if (buf_cat(&parser->val, "\n") == -1 ||
				    buf_cat(&parser->val, line) == -1)
					return -1;
			}
			res->value = line;
			return RFC5322_HEADER_CONT;
		}

		if (parser->unfold) {
			parser->val.buflen = 0;
			parser->unfold = 0;
			res->value = parser->val.buf;
		}
		return RFC5322_HEADER_END;

	case RFC5322_NONE:
	case RFC5322_HEADER_END:
		if (line && (pos = strchr(line, ':'))) {
			len = pos - line;
			if (buf_grow(&parser->hdr, len + 1) == -1)
				return -1;
			(void)memcpy(parser->hdr.buf, line, len);
			parser->hdr.buf[len] = '\0';
			parser->hdr.buflen = len + 1;
			parser->line = NULL;
			parser->next = 1;
			parser->currhdr = pos + 1;
			res->hdr = parser->hdr.buf;
			res->value = pos + 1;
			return RFC5322_HEADER_START;
		}

		return RFC5322_END_OF_HEADERS;

	case RFC5322_END_OF_HEADERS:
		if (line == NULL)
			return RFC5322_END_OF_MESSAGE;

		if (line[0] == '\0') {
			parser->line = NULL;
			parser->next = 1;
			res->value = line;
			return RFC5322_BODY_START;
		}

		errno = EINVAL;
		return -1;

	case RFC5322_BODY_START:
	case RFC5322_BODY:
		if (line == NULL)
			return RFC5322_END_OF_MESSAGE;

		parser->line = NULL;
		parser->next = 1;
		res->value = line;
		return RFC5322_BODY;

	case RFC5322_END_OF_MESSAGE:
		errno = ENOMSG;
		return -1;

	default:
		errno = EINVAL;
		return -1;
	}
}

int
rfc5322_next(struct rfc5322_parser *parser, struct rfc5322_result *res)
{
	memset(res, 0, sizeof(*res));

	if (parser->next)
		return RFC5322_NONE;

	return (parser->state = _rfc5322_next(parser, res));
}

static int
buf_alloc(struct buf *b, size_t need)
{
	char *buf;
	size_t alloc;

	if (b->buf && b->bufsz >= need)
		return 0;

	if (need >= b->bufmax) {
		errno = ERANGE;
		return -1;
	}

#define N 256
	alloc = N * (need / N) + ((need % N) ? N : 0);
#undef N
	buf = reallocarray(b->buf, alloc, 1);
	if (buf == NULL)
		return -1;

	b->buf = buf;
	b->bufsz = alloc;

	return 0;
}

static int
buf_grow(struct buf *b, size_t sz)
{
	if (SIZE_T_MAX - b->buflen <= sz) {
		errno = ERANGE;
		return -1;
	}

	return buf_alloc(b, b->buflen + sz);
}

static int
buf_cat(struct buf *b, const char *s)
{
	size_t len = strlen(s);

	if (buf_grow(b, len + 1) == -1)
		return -1;

	(void)memmove(b->buf + b->buflen, s, len + 1);
	b->buflen += len;
	return 0;
}
