/*
 * UTF8ONLY ISUPPORT: Only allow UTF8 incoming and outgoing on IRC
 * (C) Copyright 2025-.. Syzop and The UnrealIRCd Team
 * License: GPLv2 or later
 */

#include "unrealircd.h"

ModuleHeader MOD_HEADER
  = {
	"utf8only",
	"1.0.0",
	"only allow UTF8 traffic on IRC (UTF8ONLY)",
	"UnrealIRCd Team",
	"unrealircd-6",
    };

/* Forward declarations */
int utf8only_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
int utf8only_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
int utf8only_packet(Client *from, Client *to, Client *intended_to, char **msg, int *length);

/* Variables */
int utf8_only = 0;

MOD_TEST()
{
	MARK_AS_OFFICIAL_MODULE(modinfo);
	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, utf8only_config_test);

	return MOD_SUCCESS;
}

MOD_INIT()
{
	MARK_AS_OFFICIAL_MODULE(modinfo);
	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, utf8only_config_run);

	return MOD_SUCCESS;
}

MOD_LOAD()
{
	/* You may wonder what this is... two variables for the same setting?
	 * The thing is that iConf.utf8_only is external because it is also
	 * used in src/send. But... if we only set iConf.utf8_only if
	 * set::utf8-only is present then if it is first set to 1, and then
	 * later after a config change is deleted, it won't be set to 0.
	 * So we use this 'trick'.
	 * The alternative would be to do an iConf.utf8_only = 0 in MOD_INIT
	 * but then it is always zero for a short while, during rehashes,
	 * which may or may not be an issue, depending on if traffic is
	 * sent during a rehash (eg rehash output).
	 */
	iConf.utf8_only = utf8_only;

	if (iConf.utf8_only)
	{
		ISupportAdd(modinfo->handle, "UTF8ONLY", NULL);
		HookAdd(modinfo->handle, HOOKTYPE_PACKET, 0, utf8only_packet);
	}

	return MOD_SUCCESS;
}

MOD_UNLOAD()
{
	return MOD_SUCCESS;
}

int utf8only_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
{
	int errors = 0;

	if (type != CONFIG_SET)
		return 0;

	if (ce && !strcmp(ce->name, "utf8-only"))
	{
		if (!ce->value)
		{
			config_error("%s:%d: set::utf8-only: no value", ce->file->filename, ce->line_number);
			errors++;
		}
		*errs = errors;
		return errors ? -1 : 1;
	}
	return 0;
}

int utf8only_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
{
	if (type != CONFIG_SET)
		return 0;

	if (ce && !strcmp(ce->name, "utf8-only"))
	{
		iConf.utf8_only = utf8_only = config_checkval(ce->value, CFG_YESNO);
		return 1;
	}
	return 0;
}

/* Get the command from a line from an IRC client.
 * Returns "*" if not found (eg empty line, or malformed)
 */
const char *parse_get_command(const char *msg)
{
	const char *p = msg;
	static char cmd[64];

	/* Skip whitespace at beginning of the line */
	for (; *p == ' '; p++);
	/* Skip message tags (if any) */
	if (*p == '@')
	{
		for (p++; *p && (*p != ' '); p++);
		for (; *p == ' '; p++);
	}

	if (*p)
	{
		char *o = cmd;
		int sz = sizeof(cmd) - 1;
		for (; sz && *p && (*p != ' '); p++)
		{
			*o++ = *p;
			sz--;
		}
		*o++ = '\0';
	} else {
		strlcpy(cmd, "*", sizeof(cmd));
	}

	return cmd;
}

int utf8only_packet(Client *from, Client *to, Client *intended_to, char **msg, int *length)
{
	static char buf[16384];

	if (IsMe(from))
		return 0;

	if (IsServer(from) || IsUnknown(from))
	{
		/* For servers we convert the contents just in case,
		 * since it may be 'poisoned' with non-UTF8 stuff
		 * (eg if remote server does not enable UTF8ONLY).
		 * For unknown connections we do this too, because
		 * otherwise things like 'USER' with invalid UTF8
		 * may not be allowed through, which is a bit
		 * confusing to the user.
		 */
		char *ret = unrl_utf8_make_valid(*msg, buf, sizeof(buf), 0);
		if (ret != *msg)
		{
			*msg = ret;
			*length = strlen(*msg); /* Needs to be recalculated */
		}
		return 0;
	} else if (IsUser(from)) {
		/* Connected user: be strict */
		if (!unrl_utf8_validate(*msg, NULL))
		{
			const char *cmd = parse_get_command(*msg);
			if (HasCapability(from, "standard-replies"))
				sendto_one(from, NULL, ":%s FAIL %s INVALID_UTF8 :Message rejected, your IRC client MUST use UTF-8 encoding on this network",
				           me.name, cmd);
			else
				sendnumeric(from, ERR_CANNOTDOCOMMAND, cmd, "Message rejected, your IRC client MUST use UTF-8 encoding on this network");
			*msg = NULL;
			*length = 0;
			return 0;
		}
	}
	return 0;
}
