/* Copyright (c) 2008-2016 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "str.h"
#include "str-sanitize.h"
#include "istream.h"
#include "imap-parser.h"
#include "imap-quote.h"
#include "imap-id.h"

#ifdef HAVE_SYS_UTSNAME_H
#  include <sys/utsname.h>
#endif

#ifdef HAVE_UNAME
static struct utsname utsname_result;
static bool utsname_set = FALSE;

static const char *imap_id_get_uname(const char *key)
{
	if (!utsname_set) {
		utsname_set = TRUE;
		if (uname(&utsname_result) < 0) {
			i_error("uname() failed: %m");
			memset(&utsname_result, 0, sizeof(utsname_result));
		}
	}

	if (strcasecmp(key, "os") == 0)
		return utsname_result.sysname;
	if (strcasecmp(key, "os-version") == 0)
		return utsname_result.release;
	return NULL;
}
#endif

static const char *imap_id_get_default(const char *key)
{
	if (strcasecmp(key, "name") == 0)
		return PACKAGE_NAME;
	if (strcasecmp(key, "version") == 0)
		return PACKAGE_VERSION;
	if (strcasecmp(key, "support-url") == 0)
		return PACKAGE_WEBPAGE;
	if (strcasecmp(key, "support-email") == 0)
		return PACKAGE_BUGREPORT;
#ifdef HAVE_UNAME
	return imap_id_get_uname(key);
#endif
}

static const char *
imap_id_reply_generate_from_imap_args(const struct imap_arg *args)
{
	string_t *str;
	const char *key, *value;

	if (IMAP_ARG_IS_EOL(args))
		return "NIL";

	str = t_str_new(256);
	str_append_c(str, '(');
	for (; !IMAP_ARG_IS_EOL(args); args++) {
		if (!imap_arg_get_astring(args, &key)) {
			/* broken input */
			if (IMAP_ARG_IS_EOL(&args[1]))
				break;
			args++;
		} else {
			/* key */
			if (str_len(str) > 1)
				str_append_c(str, ' ');
			imap_append_quoted(str, key);
			str_append_c(str, ' ');
			/* value */
			if (IMAP_ARG_IS_EOL(&args[1])) {
				str_append(str, "NIL");
				break;
			}
			args++;
			if (!imap_arg_get_astring(args, &value))
				value = NULL;
			else {
				if (strcmp(value, "*") == 0)
					value = imap_id_get_default(key);
			}
			imap_append_nstring(str, value);
		}
	}
	if (str_len(str) == 1) {
		/* broken */
		return "NIL";
	}
	str_append_c(str, ')');
	return str_c(str);
}

const char *imap_id_reply_generate(const char *settings)
{
	struct istream *input;
	struct imap_parser *parser;
	const struct imap_arg *args;
	const char *ret;

	if (settings == NULL)
		return "NIL";

	input = i_stream_create_from_data(settings, strlen(settings));
	(void)i_stream_read(input);

	parser = imap_parser_create(input, NULL, (size_t)-1);
	if (imap_parser_finish_line(parser, 0, 0, &args) <= 0)
		ret = "NIL";
	else
		ret = imap_id_reply_generate_from_imap_args(args);

	imap_parser_unref(&parser);
	i_stream_destroy(&input);
	return ret;
}

void imap_id_log_reply_append(string_t *reply, const char *key,
			      const char *value)
{
	if (str_len(reply) > 0)
		str_append(reply, ", ");
	str_append(reply, str_sanitize(key, IMAP_ID_KEY_MAX_LEN));
	str_append_c(reply, '=');
	str_append(reply, value == NULL ? "NIL" : str_sanitize(value, 80));
}

const char *imap_id_args_get_log_reply(const struct imap_arg *args,
				       const char *settings)
{
	const char *const *keys, *key, *value;
	string_t *reply;
	bool log_all;

	if (settings == NULL || *settings == '\0')
		return NULL;
	if (!imap_arg_get_list(args, &args))
		return NULL;

	log_all = strcmp(settings, "*") == 0;
	reply = t_str_new(256);
	keys = t_strsplit_spaces(settings, " ");
	while (!IMAP_ARG_IS_EOL(&args[0]) &&
	       !IMAP_ARG_IS_EOL(&args[1])) {
		if (!imap_arg_get_string(args, &key)) {
			/* broken input */
			args += 2;
			continue;
		}
		args++;
		if (strlen(key) > 30) {
			/* broken: ID spec requires fields to be max. 30
			   octets */
			args++;
			continue;
		}

		if (log_all || str_array_icase_find(keys, key)) {
			if (!imap_arg_get_nstring(args, &value))
				value = "";
			imap_id_log_reply_append(reply, key, value);
		}
		args++;
	}
	return str_len(reply) == 0 ? NULL : str_c(reply);
}
