/*
** Copyright (C) 2001-2026 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/>.
**/

#include "zbxexpr.h"

#include "zbx_expression_constants.h"
#include "zbxstr.h"

/* macros that can be indexed */
static const char	*ex_macros[] =
{
	MVAR_INVENTORY_TYPE, MVAR_INVENTORY_TYPE_FULL,
	MVAR_INVENTORY_NAME, MVAR_INVENTORY_ALIAS, MVAR_INVENTORY_OS, MVAR_INVENTORY_OS_FULL, MVAR_INVENTORY_OS_SHORT,
	MVAR_INVENTORY_SERIALNO_A, MVAR_INVENTORY_SERIALNO_B, MVAR_INVENTORY_TAG,
	MVAR_INVENTORY_ASSET_TAG, MVAR_INVENTORY_MACADDRESS_A, MVAR_INVENTORY_MACADDRESS_B,
	MVAR_INVENTORY_HARDWARE, MVAR_INVENTORY_HARDWARE_FULL, MVAR_INVENTORY_SOFTWARE, MVAR_INVENTORY_SOFTWARE_FULL,
	MVAR_INVENTORY_SOFTWARE_APP_A, MVAR_INVENTORY_SOFTWARE_APP_B, MVAR_INVENTORY_SOFTWARE_APP_C,
	MVAR_INVENTORY_SOFTWARE_APP_D, MVAR_INVENTORY_SOFTWARE_APP_E, MVAR_INVENTORY_CONTACT, MVAR_INVENTORY_LOCATION,
	MVAR_INVENTORY_LOCATION_LAT, MVAR_INVENTORY_LOCATION_LON, MVAR_INVENTORY_NOTES, MVAR_INVENTORY_CHASSIS,
	MVAR_INVENTORY_MODEL, MVAR_INVENTORY_HW_ARCH, MVAR_INVENTORY_VENDOR, MVAR_INVENTORY_CONTRACT_NUMBER,
	MVAR_INVENTORY_INSTALLER_NAME, MVAR_INVENTORY_DEPLOYMENT_STATUS, MVAR_INVENTORY_URL_A, MVAR_INVENTORY_URL_B,
	MVAR_INVENTORY_URL_C, MVAR_INVENTORY_HOST_NETWORKS, MVAR_INVENTORY_HOST_NETMASK, MVAR_INVENTORY_HOST_ROUTER,
	MVAR_INVENTORY_OOB_IP, MVAR_INVENTORY_OOB_NETMASK, MVAR_INVENTORY_OOB_ROUTER, MVAR_INVENTORY_HW_DATE_PURCHASE,
	MVAR_INVENTORY_HW_DATE_INSTALL, MVAR_INVENTORY_HW_DATE_EXPIRY, MVAR_INVENTORY_HW_DATE_DECOMM,
	MVAR_INVENTORY_SITE_ADDRESS_A, MVAR_INVENTORY_SITE_ADDRESS_B, MVAR_INVENTORY_SITE_ADDRESS_C,
	MVAR_INVENTORY_SITE_CITY, MVAR_INVENTORY_SITE_STATE, MVAR_INVENTORY_SITE_COUNTRY, MVAR_INVENTORY_SITE_ZIP,
	MVAR_INVENTORY_SITE_RACK, MVAR_INVENTORY_SITE_NOTES, MVAR_INVENTORY_POC_PRIMARY_NAME,
	MVAR_INVENTORY_POC_PRIMARY_EMAIL, MVAR_INVENTORY_POC_PRIMARY_PHONE_A, MVAR_INVENTORY_POC_PRIMARY_PHONE_B,
	MVAR_INVENTORY_POC_PRIMARY_CELL, MVAR_INVENTORY_POC_PRIMARY_SCREEN, MVAR_INVENTORY_POC_PRIMARY_NOTES,
	MVAR_INVENTORY_POC_SECONDARY_NAME, MVAR_INVENTORY_POC_SECONDARY_EMAIL, MVAR_INVENTORY_POC_SECONDARY_PHONE_A,
	MVAR_INVENTORY_POC_SECONDARY_PHONE_B, MVAR_INVENTORY_POC_SECONDARY_CELL, MVAR_INVENTORY_POC_SECONDARY_SCREEN,
	MVAR_INVENTORY_POC_SECONDARY_NOTES,
	/* PROFILE.* is deprecated, use INVENTORY.* instead */
	MVAR_PROFILE_DEVICETYPE, MVAR_PROFILE_NAME, MVAR_PROFILE_OS, MVAR_PROFILE_SERIALNO,
	MVAR_PROFILE_TAG, MVAR_PROFILE_MACADDRESS, MVAR_PROFILE_HARDWARE, MVAR_PROFILE_SOFTWARE,
	MVAR_PROFILE_CONTACT, MVAR_PROFILE_LOCATION, MVAR_PROFILE_NOTES,
	MVAR_HOST_HOST, MVAR_HOSTNAME, MVAR_HOST_NAME, MVAR_HOST_DESCRIPTION, MVAR_PROXY_NAME, MVAR_PROXY_DESCRIPTION,
	MVAR_HOST_CONN, MVAR_HOST_DNS, MVAR_HOST_IP, MVAR_HOST_PORT, MVAR_IPADDRESS, MVAR_HOST_ID,
	MVAR_ITEM_ID, MVAR_ITEM_NAME, MVAR_ITEM_NAME_ORIG, MVAR_ITEM_DESCRIPTION, MVAR_ITEM_DESCRIPTION_ORIG,
	MVAR_ITEM_KEY, MVAR_ITEM_KEY_ORIG, MVAR_TRIGGER_KEY,
	MVAR_ITEM_LASTVALUE,
	MVAR_ITEM_STATE,
	MVAR_ITEM_VALUE, MVAR_ITEM_VALUETYPE,
	MVAR_ITEM_VALUE_TIMESTAMP, MVAR_ITEM_VALUE_TIME, MVAR_ITEM_VALUE_DATE, MVAR_ITEM_VALUE_AGE,
	MVAR_ITEM_LASTVALUE_TIMESTAMP, MVAR_ITEM_LASTVALUE_TIME, MVAR_ITEM_LASTVALUE_DATE, MVAR_ITEM_LASTVALUE_AGE,
	MVAR_ITEM_LOG_DATE, MVAR_ITEM_LOG_TIME, MVAR_ITEM_LOG_TIMESTAMP, MVAR_ITEM_LOG_AGE, MVAR_ITEM_LOG_SOURCE,
	MVAR_ITEM_LOG_SEVERITY, MVAR_ITEM_LOG_NSEVERITY, MVAR_ITEM_LOG_EVENTID,
	MVAR_FUNCTION_VALUE, MVAR_FUNCTION_RECOVERY_VALUE,
	NULL
};

const char	**zbx_get_indexable_macros(void)
{
	return ex_macros;
}

static int	substitute_macros_args(zbx_token_search_t search, char **data, char *error, size_t maxerrlen,
		zbx_macro_resolv_func_t resolver, va_list args)
{
	zbx_macro_resolv_data_t	p = {0};
	int			found, res = SUCCEED;
	char			c, *m_ptr, *replace_to = NULL;
	size_t			data_alloc, data_len;

	if (NULL == data || NULL == *data || '\0' == **data)
	{
		zabbix_log(LOG_LEVEL_DEBUG, "In %s() data:EMPTY", __func__);
		return res;
	}

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() data:'%s'", __func__, *data);

	p.token_search = search;

	if (SUCCEED != zbx_token_find(*data, p.pos, &p.token, p.token_search))
		goto out;

	data_alloc = data_len = strlen(*data) + 1;

	for (found = SUCCEED; SUCCEED == res && SUCCEED == found;
			found = zbx_token_find(*data, p.pos, &p.token, p.token_search))
	{
		int	ret = SUCCEED;

		p.inner_token = p.token;
		p.indexed = p.raw_value = p.resolved = 0;
		p.index = 1;

		p.pos = p.token.loc.l;

		switch (p.token.type)
		{
			case ZBX_TOKEN_OBJECTID:
			case ZBX_TOKEN_LLD_MACRO:
			case ZBX_TOKEN_LLD_FUNC_MACRO:
				/* neither lld nor {123123} macros are processed by this function, skip them */
				p.pos = p.token.loc.r + 1;
				continue;
			case ZBX_TOKEN_MACRO:
				if (0 != zbx_is_indexed_macro(*data, &p.token) &&
						NULL != (p.macro = zbx_macro_in_list(*data, p.token.loc,
						zbx_get_indexable_macros(), &p.index)))
				{
					p.indexed = 1;
				}
				else
				{
					p.macro = *data + p.token.loc.l;
					c = (*data)[p.token.loc.r + 1];
					(*data)[p.token.loc.r + 1] = '\0';
				}
				break;
			case ZBX_TOKEN_USER_FUNC_MACRO:
				p.raw_value = 1;
				/* user macros are not indexed */
				if (NULL == (m_ptr = zbx_get_macro_from_func(*data, &p.token.data.func_macro, NULL))
						|| SUCCEED != zbx_token_find(*data, p.token.data.func_macro.macro.l,
						&p.inner_token, p.token_search))
				{
					/* Ignore functions with macros not supporting them, but do not skip the */
					/* whole token, nested macro should be resolved in this case. */
					p.pos++;
					ret = FAIL;
				}
				p.macro = m_ptr;
				break;
			case ZBX_TOKEN_FUNC_MACRO:
				p.raw_value = 1;
				p.indexed = zbx_is_indexed_macro(*data, &p.token);
				if (NULL == (m_ptr = zbx_get_macro_from_func(*data, &p.token.data.func_macro, &p.index))
						|| SUCCEED != zbx_token_find(*data, p.token.data.func_macro.macro.l,
						&p.inner_token, p.token_search))
				{
					/* Ignore functions with macros not supporting them, but do not skip the */
					/* whole token, nested macro should be resolved in this case. */
					p.pos++;
					ret = FAIL;
				}
				p.macro = m_ptr;
				break;
			case ZBX_TOKEN_USER_MACRO:
				/* To avoid *data modification user macro resolver should be replaced with a function */
				/* that takes initial *data string and token.data.user_macro instead of p.macro as    */
				/* params.                                                                            */
				p.macro = *data + p.token.loc.l;
				c = (*data)[p.token.loc.r + 1];
				(*data)[p.token.loc.r + 1] = '\0';
				break;
			case ZBX_TOKEN_REFERENCE:
			case ZBX_TOKEN_EXPRESSION_MACRO:
				/* These macros (and probably all other in the future) must be resolved using only    */
				/* information stored in token.data union. For now, force crash if they rely on       */
				/* p.macro.                                                                           */
				p.macro = NULL;
				break;
			default:
				THIS_SHOULD_NEVER_HAPPEN;
				res = FAIL;
				continue;
		}

		if (SUCCEED == ret)
		{
			va_list	pargs;

			va_copy(pargs, args); /* copy current argument position */

			ret = resolver(&p, pargs, &replace_to, data, error, maxerrlen);

			va_end(pargs);

			if (SUCCEED < ret) continue; /* resolver did everything */

			if ((ZBX_TOKEN_FUNC_MACRO == p.token.type || ZBX_TOKEN_USER_FUNC_MACRO == p.token.type) &&
					NULL != replace_to)
			{
				if (SUCCEED != (ret = zbx_calculate_macro_function(*data, &p.token.data.func_macro,
						&replace_to)))
				{
					zbx_free(replace_to);
				}
			}
		}

		if (FAIL == ret)
		{
			zabbix_log(LOG_LEVEL_DEBUG, "cannot resolve macro '%.*s'",
					(int)(p.token.loc.r - p.token.loc.l + 1), *data + p.token.loc.l);

			if (ZBX_TOKEN_MACRO == p.token.type && SUCCEED == zbx_is_strict_macro(p.macro))
			{
				if (NULL != error)
				{
					/* return error if strict macro resolving failed */
					zbx_snprintf(error, maxerrlen, "Invalid macro '%.*s' value",
							(int)(p.token.loc.r - p.token.loc.l + 1),
							*data + p.token.loc.l);

					res = FAIL;
				}
			}

			replace_to = zbx_strdup(replace_to, STR_UNKNOWN_VARIABLE);
		}

		if (ZBX_TOKEN_USER_MACRO == p.token.type || (ZBX_TOKEN_MACRO == p.token.type && 0 == p.indexed))
			(*data)[p.token.loc.r + 1] = c;

		if (NULL != replace_to)
		{
			p.pos = p.token.loc.r;

			p.pos += zbx_replace_mem_dyn(data, &data_alloc, &data_len, p.token.loc.l,
					p.token.loc.r - p.token.loc.l + 1, replace_to, strlen(replace_to));
			zbx_free(replace_to);
		}

		if (ZBX_TOKEN_FUNC_MACRO == p.token.type || ZBX_TOKEN_USER_FUNC_MACRO == p.token.type)
			zbx_free(m_ptr);

		p.pos++;
	}

out:
	zabbix_log(LOG_LEVEL_DEBUG, "End %s()", __func__);

	return res;
}

/******************************************************************************
 *                                                                            *
 * Purpose: substitutes macros                                                *
 *                                                                            *
 * Parameters: data      - [IN/OUT] pointer to data where macros should be    *
 *                                  resolved                                  *
 *             error     - [OUT] pre-allocated buffer for error message       *
 *             maxerrlen - [IN] size of pre-allocated error message buffer    *
 *             resolver  - [IN] callback to macro resolver function           *
 *             ...       - [IN/OUT] variadic arguments passed to macro        *
 *                                  resolver function to identify data        *
 *                                                                            *
 * Return value: SUCCEED  - all recognised macros were resolved               *
 *               FAIL     - macro resolving failed                            *
 *                                                                            *
 * Note: When macro is recognised but has no value then it will be resolved   *
 *       as *UNKNOWN*.                                                        *
 *                                                                            *
 * Macro resolver parameters:                                                 *
 *             p          - [IN] macro resolver data structure                *
 *             args       - [IN] list of variadic parameters passed from      *
 *                               zbx_substitute_macros_* function             *
 *             replace_to - [OUT] pointer to value to replace macro with      *
 *                                Note: value will be freed.                  *
 *             data       - [IN/OUT] pointer to input data string             *
 *             error      - [OUT] pointer to pre-allocated error message      *
 *                                buffer                                      *
 *             maxerrlen  - [IN] size of error message buffer                 *
 *                                                                            *
 * Macro resolver return value:                                               *
 *               SUCCEED         - when recognised macro were found and it's  *
 *                                 value were assigned to replace_to pointer  *
 *               SUCCEED_PARTIAL - when resolver itself did everything, no    *
 *                                 further checks are required *special case* *
 *               FAIL            - when data gathering failed: value will be  *
 *                                 get to *UNKNOWN*                           *
 *                                                                            *
 * Note: When macro is not recognised then do not return FAIL, just keep      *
 *       replace_to unassigned so other macro resolvers may resolve it's      *
 *       macros.                                                              *
 *                                                                            *
 ******************************************************************************/
int	zbx_substitute_macros(char **data, char *error, size_t maxerrlen, zbx_macro_resolv_func_t resolver, ...)
{
	int	ret;
	va_list	args;

	va_start(args, resolver);

	ret = substitute_macros_args(ZBX_TOKEN_SEARCH_BASIC, data, error, maxerrlen, resolver, args);

	va_end(args);

	return ret;
}
