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

#include "lib.h"
#include "array.h"
#include "istream.h"
#include "istream-concat.h"
#include "istream-sized.h"
#include "istream-base64.h"
#include "istream-attachment-connector.h"

struct istream_attachment_connector {
	pool_t pool;
	struct istream *base_input;
	uoff_t base_input_offset, msg_size;

	uoff_t encoded_offset;
	ARRAY(struct istream *) streams;
};

struct istream_attachment_connector *
istream_attachment_connector_begin(struct istream *base_input, uoff_t msg_size)
{
	struct istream_attachment_connector *conn;
	pool_t pool;

	pool = pool_alloconly_create("istream-attachment-connector", 1024);
	conn = p_new(pool, struct istream_attachment_connector, 1);
	conn->pool = pool;
	conn->base_input = base_input;
	conn->base_input_offset = base_input->v_offset;
	conn->msg_size = msg_size;
	p_array_init(&conn->streams, pool, 8);
	i_stream_ref(conn->base_input);
	return conn;
}

int istream_attachment_connector_add(struct istream_attachment_connector *conn,
				     struct istream *decoded_input,
				     uoff_t start_offset, uoff_t encoded_size,
				     unsigned int base64_blocks_per_line,
				     bool base64_have_crlf,
				     const char **error_r)
{
	struct istream *input, *input2;
	uoff_t base_prefix_size;

	if (start_offset < conn->encoded_offset) {
		*error_r = t_strdup_printf(
			"Attachment %s points before the previous attachment "
			"(%"PRIuUOFF_T" < %"PRIuUOFF_T")",
			i_stream_get_name(decoded_input),
			start_offset, conn->encoded_offset);
		return -1;
	}
	base_prefix_size = start_offset - conn->encoded_offset;
	if (start_offset + encoded_size > conn->msg_size) {
		*error_r = t_strdup_printf(
			"Attachment %s points outside message "
			"(%"PRIuUOFF_T" + %"PRIuUOFF_T" > %"PRIuUOFF_T")",
			i_stream_get_name(decoded_input),
			start_offset, encoded_size,
			conn->msg_size);
		return -1;
	}

	if (base_prefix_size > 0) {
		/* add a part of the base message before the attachment */
		input = i_stream_create_min_sized_range(conn->base_input,
			conn->base_input_offset, base_prefix_size);
		i_stream_set_name(input, t_strdup_printf("%s middle",
			i_stream_get_name(conn->base_input)));
		array_push_back(&conn->streams, &input);
		conn->base_input_offset += base_prefix_size;
		conn->encoded_offset += base_prefix_size;
	}
	conn->encoded_offset += encoded_size;

	if (base64_blocks_per_line == 0) {
		input = decoded_input;
		i_stream_ref(input);
	} else {
		input = i_stream_create_base64_encoder(decoded_input,
						       base64_blocks_per_line*4,
						       base64_have_crlf);
		i_stream_set_name(input, t_strdup_printf("%s[base64:%u b/l%s]",
				  i_stream_get_name(decoded_input),
				  base64_blocks_per_line,
				  base64_have_crlf ? ",crlf" : ""));
	}
	input2 = i_stream_create_sized(input, encoded_size);
	array_push_back(&conn->streams, &input2);
	i_stream_unref(&input);
	return 0;
}

static void
istream_attachment_connector_free(struct istream_attachment_connector *conn)
{
	struct istream *stream;

	array_foreach_elem(&conn->streams, stream)
		i_stream_unref(&stream);
	i_stream_unref(&conn->base_input);
	pool_unref(&conn->pool);
}

struct istream *
istream_attachment_connector_finish(struct istream_attachment_connector **_conn)
{
	struct istream_attachment_connector *conn = *_conn;
	struct istream **inputs, *input;
	uoff_t trailer_size;

	*_conn = NULL;

	if (conn->base_input_offset != conn->msg_size) {
		i_assert(conn->base_input_offset < conn->msg_size);

		if (conn->msg_size != UOFF_T_MAX) {
			trailer_size = conn->msg_size - conn->encoded_offset;
			input = i_stream_create_sized_range(conn->base_input,
							    conn->base_input_offset,
							    trailer_size);
			i_stream_set_name(input, t_strdup_printf(
				"%s trailer", i_stream_get_name(conn->base_input)));
		} else {
			input = i_stream_create_range(conn->base_input,
						      conn->base_input_offset,
						      UOFF_T_MAX);
		}
		array_push_back(&conn->streams, &input);
	}
	array_append_zero(&conn->streams);

	inputs = array_front_modifiable(&conn->streams);
	input = i_stream_create_concat(inputs);

	istream_attachment_connector_free(conn);
	return input;
}

void istream_attachment_connector_abort(struct istream_attachment_connector **_conn)
{
	struct istream_attachment_connector *conn = *_conn;

	*_conn = NULL;

	istream_attachment_connector_free(conn);
}
