/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "istream-private.h"
#include "istream-metawrap.h"

#define METAWRAP_MAX_METADATA_LINE_LEN 8192

struct metawrap_istream {
	struct istream_private istream;
	metawrap_callback_t *callback;
	void *context;

	uoff_t start_offset, pending_seek;
	bool in_metadata;
};

static int metadata_header_read(struct metawrap_istream *mstream)
{
	char *line, *p;

	while ((line = i_stream_read_next_line(mstream->istream.parent)) != NULL) {
		if (*line == '\0') {
			mstream->callback(NULL, NULL, mstream->context);
			return 1;
		}
		p = strchr(line, ':');
		if (p == NULL) {
			io_stream_set_error(&mstream->istream.iostream,
				"Metadata header line is missing ':' at offset %"PRIuUOFF_T,
				mstream->istream.istream.v_offset);
			mstream->istream.istream.stream_errno = EINVAL;
			return -1;
		}
		*p++ = '\0';
		mstream->callback(line, p, mstream->context);
	}
	if (mstream->istream.parent->eof) {
		if (mstream->istream.parent->stream_errno != 0) {
			mstream->istream.istream.stream_errno =
				mstream->istream.parent->stream_errno;
		} else {
			io_stream_set_error(&mstream->istream.iostream,
				"Metadata header is missing ending line at offset %"PRIuUOFF_T,
				mstream->istream.istream.v_offset);
			mstream->istream.istream.stream_errno = EPIPE;
			return -1;
		}
		mstream->istream.istream.eof = TRUE;
		return -1;
	}
	i_assert(!mstream->istream.parent->blocking);
	return 0;
}

static ssize_t i_stream_metawrap_read(struct istream_private *stream)
{
	struct metawrap_istream *mstream = (struct metawrap_istream *)stream;
	int ret;

	i_stream_seek(stream->parent, mstream->start_offset +
		      stream->istream.v_offset);

	if (mstream->in_metadata) {
		size_t prev_max_size = i_stream_get_max_buffer_size(stream->parent);

		i_stream_set_max_buffer_size(stream->parent, METAWRAP_MAX_METADATA_LINE_LEN);
		ret = metadata_header_read(mstream);
		i_stream_set_max_buffer_size(stream->parent, prev_max_size);

		i_assert(stream->istream.v_offset == 0);
		mstream->start_offset = stream->parent->v_offset;
		if (ret <= 0)
			return ret;
		/* this stream is kind of silently skipping over the metadata */
		stream->start_offset += mstream->start_offset;
		mstream->in_metadata = FALSE;
		if (mstream->pending_seek != 0) {
			i_stream_seek(&stream->istream, mstream->pending_seek);
			return i_stream_read_memarea(&stream->istream);
		}
	}
	/* after metadata header it's all just passthrough */
	return i_stream_read_copy_from_parent(&stream->istream);
}

static void
i_stream_metawrap_seek(struct istream_private *stream,
		       uoff_t v_offset, bool mark ATTR_UNUSED)
{
	struct metawrap_istream *mstream = (struct metawrap_istream *)stream;

	if (!mstream->in_metadata) {
		/* already read through metadata. we can skip directly. */
		stream->istream.v_offset = v_offset;
		mstream->pending_seek = 0;
	} else {
		/* we need to read through the metadata first */
		mstream->pending_seek = v_offset;
		stream->istream.v_offset = 0;
	}
	stream->skip = stream->pos = 0;
}

static int i_stream_metawrap_stat(struct istream_private *stream, bool exact)
{
	struct metawrap_istream *mstream = (struct metawrap_istream *)stream;
	const struct stat *st;
	int ret;

	if (i_stream_stat(stream->parent, exact, &st) < 0) {
		stream->istream.stream_errno = stream->parent->stream_errno;
		return -1;
	}
	stream->statbuf = *st;

	if (mstream->in_metadata) {
		ret = i_stream_read_memarea(&stream->istream);
		if (ret < 0 && stream->istream.stream_errno != 0)
			return -1;
		if (ret == 0) {
			stream->statbuf.st_size = -1;
			return 0;
		}
	}
	i_assert((uoff_t)stream->statbuf.st_size >= mstream->start_offset);
	stream->statbuf.st_size -= mstream->start_offset;
	return 0;
}

struct istream *
i_stream_create_metawrap(struct istream *input,
			 metawrap_callback_t *callback, void *context)
{
	struct metawrap_istream *mstream;

	mstream = i_new(struct metawrap_istream, 1);
	mstream->istream.max_buffer_size = input->real_stream->max_buffer_size;

	mstream->istream.read = i_stream_metawrap_read;
	mstream->istream.seek = i_stream_metawrap_seek;
	mstream->istream.stat = input->seekable ? i_stream_metawrap_stat : NULL;

	mstream->istream.istream.readable_fd = input->readable_fd;
	mstream->istream.istream.blocking = input->blocking;
	mstream->istream.istream.seekable = input->seekable;
	mstream->in_metadata = TRUE;
	mstream->callback = callback;
	mstream->context = context;
	return i_stream_create(&mstream->istream, input,
			       i_stream_get_fd(input), 0);
}
