/* Copyright (c) Dovecot authors, see top-level COPYING file */

#include "lib.h"
#include "istream.h"
#include "message-parser.h"
#include "message-size.h"

int message_get_header_size(struct istream *input, struct message_size *hdr,
			    bool *has_nuls_r)
{
	const unsigned char *msg;
	size_t i, size, startpos, missing_cr_count;
	int ret;

	memset(hdr, 0, sizeof(struct message_size));
	*has_nuls_r = FALSE;

	missing_cr_count = 0; startpos = 0;
	while ((ret = i_stream_read_bytes(input, &msg, &size, startpos + 1)) > 0) {
		for (i = startpos; i < size; i++) {
			if (msg[i] != '\n') {
				if (msg[i] == '\0')
					*has_nuls_r = TRUE;
				continue;
			}

			hdr->lines++;
			if (i == 0 || msg[i-1] != '\r') {
				/* missing CR */
				missing_cr_count++;
			}

			if (i == 0 || (i == 1 && msg[i-1] == '\r')) {
				/* no headers at all */
				break;
			}

			if ((i > 0 && msg[i-1] == '\n') ||
			    (i > 1 && msg[i-2] == '\n' && msg[i-1] == '\r')) {
				/* \n\n or \n\r\n - end of headers */
				break;
			}
		}

		if (i < size) {
			/* end of header */
			startpos = i+1;
			break;
		}

		/* leave the last two characters, they may be \r\n */
		startpos = size == 1 ? 1 : 2;
		i_stream_skip(input, i - startpos);

		hdr->physical_size += i - startpos;
	}
	i_assert(ret == -1 || ret > 0);

	ret = input->stream_errno != 0 ? -1 : 0;
	i_stream_skip(input, startpos);
	hdr->physical_size += startpos;

	hdr->virtual_size = hdr->physical_size + missing_cr_count;
	i_assert(hdr->virtual_size >= hdr->physical_size);
	return ret;
}

int message_get_body_size(struct istream *input, struct message_size *body,
			  bool *has_nuls_r)
{
	const unsigned char *msg;
	size_t i, size, missing_cr_count;
	int ret;

	memset(body, 0, sizeof(struct message_size));
	*has_nuls_r = FALSE;

	missing_cr_count = 0;
	if ((ret = i_stream_read_more(input, &msg, &size)) <= 0) {
		i_assert(ret == -1);
		return ret < 0 && input->stream_errno != 0 ? -1 : 0;
	}

	if (msg[0] == '\n')
		missing_cr_count++;

	do {
		for (i = 1; i < size; i++) {
			if (msg[i] > '\n')
				continue;

			if (msg[i] == '\n') {
				if (msg[i-1] != '\r') {
					/* missing CR */
					missing_cr_count++;
				}

				/* increase after making sure we didn't break
				   at virtual \r */
				body->lines++;
			} else if (msg[i] == '\0') {
				*has_nuls_r = TRUE;
			}
		}

		/* leave the last character, it may be \r */
		i_stream_skip(input, i - 1);
		body->physical_size += i - 1;
	} while ((ret = i_stream_read_bytes(input, &msg, &size, 2)) > 0);
	i_assert(ret == -1);

	ret = input->stream_errno != 0 ? -1 : 0;

	i_stream_skip(input, 1);
	body->physical_size++;

	body->virtual_size = body->physical_size + missing_cr_count;
	i_assert(body->virtual_size >= body->physical_size);
	return ret;
}

void message_size_add(struct message_size *dest,
		      const struct message_size *src)
{
	dest->virtual_size += src->virtual_size;
	dest->physical_size += src->physical_size;
	dest->lines += src->lines;
}

int message_skip_virtual(struct istream *input, uoff_t virtual_skip,
			 bool *last_virtual_cr_r)
{
	const unsigned char *msg;
	size_t i, size;
	bool cr_skipped = FALSE;
	int ret;

	*last_virtual_cr_r = FALSE;
	if (virtual_skip == 0)
		return 0;

	while ((ret = i_stream_read_bytes(input, &msg, &size, 1)) > 0) {
		size = I_MIN(virtual_skip, size);
		const unsigned char *p = memchr(msg, '\n', size);
		if (p == NULL) {
			i_stream_skip(input, size);
			virtual_skip -= size;
			if (virtual_skip == 0)
				return 0;
			continue;
		}
		i = p - msg;
		virtual_skip -= i + 1;

		/* LF */
		if ((i == 0 && !cr_skipped) ||
		    (i > 0 && msg[i-1] != '\r')) {
			if (virtual_skip == 0) {
				/* CR/LF boundary */
				*last_virtual_cr_r = TRUE;
			} else {
				virtual_skip--;
				i++;
			}
		} else {
			i++;
		}
		i_stream_skip(input, i);
		if (virtual_skip == 0)
			return 0;

		i_assert(i > 0);
		cr_skipped = msg[i-1] == '\r';
	}
	i_assert(ret == -1);
	return input->stream_errno == 0 ? 0 : -1;
}
