/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
 */

#include "lib.h"
#include "ostream.h"
#include "istream.h"
#include "iostream.h"

#include "sieve-script.h"
#include "sieve-storage.h"

#include "managesieve-common.h"
#include "managesieve-commands.h"

struct cmd_getscript_context {
	struct client *client;
	struct client_command_context *cmd;
	struct sieve_storage *storage;
	uoff_t script_size, script_offset;

	struct sieve_script *script;
	struct istream *script_stream;

	unsigned int failed:1;
};

static bool cmd_getscript_finish(struct cmd_getscript_context *ctx)
{
	struct client *client = ctx->client;

	if ( ctx->script != NULL )
		sieve_script_unref(&ctx->script);

	if ( ctx->failed ) {
		if ( client->output->closed ) {
			client_disconnect(client, "Disconnected");
			return TRUE;
		}

		client_send_storage_error(client, client->storage);
		return TRUE;
	}

	client->get_count++;
	client->get_bytes += ctx->script_size;

	client_send_line(client, "");
	client_send_ok(client, "Getscript completed.");
	return TRUE;
}

static bool cmd_getscript_continue(struct client_command_context *cmd)
{
	struct client *client = cmd->client;
	struct cmd_getscript_context *ctx = cmd->context;
	off_t ret;

	ret = o_stream_send_istream(client->output, ctx->script_stream);

	if ( ret < 0 ) {
		if ( ctx->script_stream->stream_errno != 0 ) {
			sieve_storage_set_critical(ctx->storage,
				"o_stream_send_istream() failed for script `%s' from %s: %s",
				sieve_script_name(ctx->script),
				sieve_script_location(ctx->script),
				i_stream_get_error(ctx->script_stream));
		} else {
			client_disconnect(ctx->client,
				io_stream_get_disconnect_reason
					(client->input, client->output));
		}
		ctx->failed = TRUE;
		return cmd_getscript_finish(ctx);
	}

	ctx->script_offset += ret;

	if ( ctx->script_offset != ctx->script_size && !ctx->failed ) {
		/* unfinished */
		if ( !i_stream_have_bytes_left(ctx->script_stream) ) {
			/* Input stream gave less data than expected */
			sieve_storage_set_critical(ctx->storage,
				"GETSCRIPT for script `%s' from %s got too little data: "
				"%"PRIuUOFF_T" vs %"PRIuUOFF_T, sieve_script_name(ctx->script),
				sieve_script_location(ctx->script), ctx->script_offset, ctx->script_size);

			client_disconnect(ctx->client, "GETSCRIPT failed");
			ctx->failed = TRUE;
			return cmd_getscript_finish(ctx);
		}

		return FALSE;
	}

	return cmd_getscript_finish(ctx);
}

bool cmd_getscript(struct client_command_context *cmd)
{
	struct client *client = cmd->client;
	struct cmd_getscript_context *ctx;
	const char *scriptname;
	enum sieve_error error;

	/* <scriptname> */
	if ( !client_read_string_args(cmd, TRUE, 1, &scriptname) )
		return FALSE;

	ctx = p_new(cmd->pool, struct cmd_getscript_context, 1);
	ctx->cmd = cmd;
	ctx->client = client;
	ctx->storage = client->storage;
	ctx->failed = FALSE;

	ctx->script = sieve_storage_open_script
		(client->storage, scriptname, NULL);
	if (ctx->script == NULL) {
		ctx->failed = TRUE;
		return cmd_getscript_finish(ctx);
	}

	if ( sieve_script_get_stream
		(ctx->script, &ctx->script_stream, &error) < 0 ) {
		if ( error == SIEVE_ERROR_NOT_FOUND )
			sieve_storage_set_error(client->storage, error, "Script does not exist.");
		ctx->failed = TRUE;
		return cmd_getscript_finish(ctx);
	}

	if ( sieve_script_get_size(ctx->script, &ctx->script_size) <= 0 ) {
		sieve_storage_set_critical(ctx->storage,
			"failed to obtain script size for script `%s' from %s",
			sieve_script_name(ctx->script), sieve_script_location(ctx->script));
		ctx->failed = TRUE;
		return cmd_getscript_finish(ctx);
	}

	ctx->script_offset = 0;

	client_send_line
		(client, t_strdup_printf("{%"PRIuUOFF_T"}", ctx->script_size));

	client->command_pending = TRUE;
	cmd->func = cmd_getscript_continue;
	cmd->context = ctx;

	return cmd_getscript_continue(cmd);
}
