/*
** Copyright (C) 2001-2025 Zabbix SIA
**
** This program is free software: you can redistribute it and/or modify it under the terms of
** the GNU Affero General Public License as published by the Free Software Foundation, version 3.
**
** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
** See the GNU Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License along with this program.
** If not, see <https://www.gnu.org/licenses/>.
**/

/* this file is for the minimal possible set of string related functions that are used by libzbxcommon.a */
/* or libraries/files it depends on */

#include "zbxcommon.h"

/******************************************************************************
 *                                                                            *
 * Purpose: Secure version of vsnprintf function.                             *
 *          Add zero character at the end of string.                          *
 *                                                                            *
 * Parameters: str   - [IN/OUT] destination buffer pointer                    *
 *             count - [IN] size of destination buffer                        *
 *             fmt   - [IN] format                                            *
 *                                                                            *
 * Return value: the number of characters in the output buffer                *
 *               (not including the trailing '\0')                            *
 *                                                                            *
 ******************************************************************************/
size_t	zbx_vsnprintf(char *str, size_t count, const char *fmt, va_list args)
{
	int	written_len = 0;

	if (0 < count)
	{
		if (0 > (written_len = vsnprintf(str, count, fmt, args)))
			written_len = (int)count - 1;		/* count an output error as a full buffer */
		else
			written_len = MIN(written_len, (int)count - 1);		/* result could be truncated */
	}
	str[written_len] = '\0';	/* always write '\0', even if buffer size is 0 or vsnprintf() error */

	return (size_t)written_len;
}

int	zbx_vsnprintf_check_len(const char *fmt, va_list args)
{
	int	rv;

	if (0 > (rv = vsnprintf(NULL, 0, fmt, args)))
	{
		THIS_SHOULD_NEVER_HAPPEN;
		exit(EXIT_FAILURE);
	}

	return rv;
}

/******************************************************************************
 *                                                                            *
 * Purpose: dynamical formatted output conversion                             *
 *                                                                            *
 * Return value: formatted string                                             *
 *                                                                            *
 * Comments: returns a pointer to allocated memory                            *
 *                                                                            *
 ******************************************************************************/
char	*zbx_dvsprintf(char *dest, const char *f, va_list args)
{
	char	*string = NULL;
	int	n, size = MAX_STRING_LEN >> 1;

	va_list curr;

	while (1)
	{
		string = (char *)zbx_malloc(string, size);

		va_copy(curr, args);
		n = vsnprintf(string, size, f, curr);
		va_end(curr);

		if (0 <= n && n < size)
			break;

		/* result was truncated */
		if (-1 == n)
			size = size * 3 / 2 + 1;	/* the length is unknown */
		else
			size = n + 1;	/* n bytes + trailing '\0' */

		zbx_free(string);
	}

	zbx_free(dest);

	return string;
}

/******************************************************************************
 *                                                                            *
 * Purpose: dynamical formatted output conversion                             *
 *                                                                            *
 * Return value: formatted string                                             *
 *                                                                            *
 * Comments: returns a pointer to allocated memory                            *
 *                                                                            *
 ******************************************************************************/
char	*zbx_dsprintf(char *dest, const char *f, ...)
{
	char	*string;
	va_list args;

	va_start(args, f);

	string = zbx_dvsprintf(dest, f, args);

	va_end(args);

	return string;
}

/******************************************************************************
 *                                                                            *
 * Purpose: Copy src to string dst of size siz. At most siz - 1 characters    *
 *          will be copied. Always null terminates (unless siz == 0).         *
 *                                                                            *
 * Return value: the number of characters copied (excluding the null byte)    *
 *                                                                            *
 ******************************************************************************/
size_t	zbx_strlcpy(char *dst, const char *src, size_t siz)
{
	const char	*s = src;

	if (0 != siz)
	{
		while (0 != --siz && '\0' != *s)
			*dst++ = *s++;

		*dst = '\0';
	}

	return s - src;	/* count does not include null */
}

/******************************************************************************
 *                                                                            *
 * Purpose: Secure version of snprintf function.                              *
 *          Add zero character at the end of string.                          *
 *                                                                            *
 * Parameters: str - destination buffer pointer                               *
 *             count - size of destination buffer                             *
 *             fmt - format                                                   *
 *                                                                            *
 ******************************************************************************/
size_t	zbx_snprintf(char *str, size_t count, const char *fmt, ...)
{
	size_t	written_len;
	va_list	args;

	va_start(args, fmt);
	written_len = zbx_vsnprintf(str, count, fmt, args);
	va_end(args);

	return written_len;
}

#if defined(__hpux)
#include "zbxlog.h"
	/* On HP-UX 11.23 vsnprintf(NULL, 0, fmt, args) cannot be used to     */
	/* determine the required buffer size - the result is program crash   */
	/* (ZBX-23404). Also, it returns -1 if buffer is too small.           */
	/* On HP-UX 11.31 vsnprintf() works as expected.                      */
	/* Agent can be compiled on HP-UX 11.23 but can be running on         */
	/* HP-UX 11.31 - it needs to adapt to vsnprintf() at runtime.         */

static int	vsnprintf_small_buf_test(const char *fmt, ...)
{
	char	buf[4];	/* large enough to store "ABC"+'\0' without corrupting stack */
	int	res;
	va_list	args;

	va_start(args, fmt);
	/* vsnprintf() with too small buffer, only "A"+'\0' can fit */
	res = vsnprintf(buf, sizeof(buf) - 2, fmt, args);
	va_end(args);

	return res;
}

#define VSNPRINTF_UNKNOWN	-1
#define VSNPRINTF_NOT_C99	0
#define VSNPRINTF_IS_C99	1

static int	test_vsnprintf(void)
{
	int	res = vsnprintf_small_buf_test("%s", "ABC");

	zabbix_log(LOG_LEVEL_DEBUG, "vsnprintf() returned %d", res);

	if (0 < res)
		return VSNPRINTF_IS_C99;
	else if (-1 == res)
		return VSNPRINTF_NOT_C99;

	zabbix_log(LOG_LEVEL_CRIT, "vsnprintf() returned %d", res);

	THIS_SHOULD_NEVER_HAPPEN;
	exit(EXIT_FAILURE);
}

int	zbx_hpux_vsnprintf_is_c99(void)
{
	static int	is_c99_vsnprintf = VSNPRINTF_UNKNOWN;

	if (VSNPRINTF_UNKNOWN == is_c99_vsnprintf)
		is_c99_vsnprintf = test_vsnprintf();

	return (VSNPRINTF_IS_C99 == is_c99_vsnprintf) ? SUCCEED : FAIL;
}
#undef VSNPRINTF_UNKNOWN
#undef VSNPRINTF_NOT_C99
#undef VSNPRINTF_IS_C99
#endif

/******************************************************************************
 *                                                                            *
 * Purpose: Secure version of snprintf function.                              *
 *          Add zero character at the end of string.                          *
 *          Reallocs memory if not enough.                                    *
 *                                                                            *
 * Parameters: str       - [IN/OUT] destination buffer pointer                *
 *             alloc_len - [IN/OUT] already allocated memory                  *
 *             offset    - [IN/OUT] offset for writing                        *
 *             fmt       - [IN] format                                        *
 *                                                                            *
 ******************************************************************************/
void	zbx_snprintf_alloc(char **str, size_t *alloc_len, size_t *offset, const char *fmt, ...)
{
	va_list	args;
	size_t	avail_len, written_len;

#if defined(__hpux)
	if (SUCCEED != zbx_hpux_vsnprintf_is_c99())
	{
#define INITIAL_ALLOC_LEN	128
		int	bytes_written = 0;

		if (NULL == *str)
		{
			*alloc_len = INITIAL_ALLOC_LEN;
			*str = (char *)zbx_malloc(NULL, *alloc_len);
			*offset = 0;
		}

		while (1)
		{
			avail_len = *alloc_len - *offset;
			va_start(args, fmt);
			bytes_written = vsnprintf(*str + *offset, avail_len, fmt, args);
			va_end(args);

			if (0 <= bytes_written)
				break;

			if (-1 == bytes_written)
			{
				*alloc_len *= 2;
				*str = (char *)zbx_realloc(*str, *alloc_len);
				continue;
			}

			zabbix_log(LOG_LEVEL_CRIT, "vsnprintf() returned %d", bytes_written);

			THIS_SHOULD_NEVER_HAPPEN;
			exit(EXIT_FAILURE);
		}

		*offset += bytes_written;

		return;
#undef INITIAL_ALLOC_LEN
	}
	/* HP-UX vsnprintf() looks C99-compliant, proceed with common implementation */
#endif
retry:
	if (NULL == *str)
	{
		va_start(args, fmt);

		/* zbx_vsnprintf_check_len() cannot return negative result. */
		/* '\0' + one byte to prevent operation retry. */
		*alloc_len = (size_t)zbx_vsnprintf_check_len(fmt, args) + 2;

		va_end(args);
		*offset = 0;
		*str = (char *)zbx_malloc(*str, *alloc_len);
	}

	avail_len = *alloc_len - *offset;
	va_start(args, fmt);
	written_len = zbx_vsnprintf(*str + *offset, avail_len, fmt, args);
	va_end(args);

	if (written_len == avail_len - 1)
	{
		*alloc_len *= 2;
		*str = (char *)zbx_realloc(*str, *alloc_len);

		goto retry;
	}

	*offset += written_len;
}

#if defined(_WINDOWS) || defined(__MINGW32__)
/* convert from selected code page to unicode */
static wchar_t	*zbx_to_unicode(unsigned int codepage, const char *cp_string)
{
	wchar_t	*wide_string = NULL;
	int	wide_size;

	wide_size = MultiByteToWideChar(codepage, 0, cp_string, -1, NULL, 0);
	wide_string = (wchar_t *)zbx_malloc(wide_string, (size_t)wide_size * sizeof(wchar_t));

	/* convert from cp_string to wide_string */
	MultiByteToWideChar(codepage, 0, cp_string, -1, wide_string, wide_size);

	return wide_string;
}

/* convert from Windows ANSI code page to unicode */
wchar_t	*zbx_acp_to_unicode(const char *acp_string)
{
	return zbx_to_unicode(CP_ACP, acp_string);
}

/* convert from UTF-8 to unicode */
wchar_t	*zbx_utf8_to_unicode(const char *utf8_string)
{
	return zbx_to_unicode(CP_UTF8, utf8_string);
}

/* convert from Windows OEM code page to unicode */
wchar_t	*zbx_oemcp_to_unicode(const char *oemcp_string)
{
	return zbx_to_unicode(CP_OEMCP, oemcp_string);
}
#endif