/*
** 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/>.
**/

#include "async_http.h"
#include "zbxcommon.h"
#include "zbxsysinc.h"
#include "zbxip.h"
#include "zbx_discoverer_constants.h"
#ifdef HAVE_LIBCURL
static int	http_task_process(short event, void *data, int *fd, struct evutil_addrinfo **current_ai,
		const char *addr, char *dnserr, struct event *timeout_event)
{
	int					 task_ret = ZBX_ASYNC_TASK_STOP;
	zbx_discovery_async_http_context_t	*http_context = (zbx_discovery_async_http_context_t *)data;

	ZBX_UNUSED(fd);
	ZBX_UNUSED(dnserr);
	ZBX_UNUSED(timeout_event);
	ZBX_UNUSED(current_ai);

	if (ZBX_ASYNC_HTTP_STEP_RDNS == http_context->step)
	{
		if (NULL != addr)
			http_context->reverse_dns = zbx_strdup(NULL, addr);

		goto stop;
	}

	if (0 != (event & EV_TIMEOUT))
		goto stop;

	if (ZABBIX_ASYNC_RESOLVE_REVERSE_DNS_YES == http_context->resolve_reverse_dns)
	{
		task_ret = ZBX_ASYNC_TASK_RESOLVE_REVERSE;
		http_context->step = ZBX_ASYNC_HTTP_STEP_RDNS;
	}
stop:
	return task_ret;
}

void	process_http_response(CURL *easy_handle, CURLcode err, void *arg)
{
	zbx_discovery_async_http_context_t	*http_context;
	discovery_poller_config_t		*poller_config = (discovery_poller_config_t *)arg;
	CURLcode				err_info;

	if (CURLE_OK != (err_info = curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, &http_context)))
	{
		zabbix_log(LOG_LEVEL_CRIT, "Cannot get pointer to private data: %s", curl_easy_strerror(err_info));
		THIS_SHOULD_NEVER_HAPPEN;
	}

	if (CURLE_OK != err)
	{
		http_context->res = FAIL;
		process_http_result(http_context);
	}
	else
	{
		http_context->res = SUCCEED;
		zbx_async_poller_add_task(poller_config->base, poller_config->dnsbase,
				http_context->async_result->dresult->ip, http_context, http_context->config_timeout,
				http_task_process, process_http_result);
	}
}

void	zbx_discovery_async_http_context_destroy(zbx_discovery_async_http_context_t *http_ctx)
{
	curl_easy_cleanup(http_ctx->easyhandle);
	zbx_free(http_ctx->reverse_dns);
	zbx_free(http_ctx);
}

int	zbx_discovery_async_check_http(CURLM *curl_mhandle, const char *config_source_ip, int timeout, const char *ip,
		unsigned short port, unsigned char type, zbx_discovery_async_http_context_t *http_ctx, char **error)
{
	CURLcode	err;
	CURLMcode	merr;
	char		url[MAX_STRING_LEN];
	CURLoption	opt;

	if (NULL == (http_ctx->easyhandle = curl_easy_init()))
	{
		*error = zbx_strdup(*error, "cannot initialize cURL library");
		return FAIL;
	}

	zbx_snprintf(url, sizeof(url), SUCCEED == zbx_is_ip6(ip) ? "%s[%s]" : "%s%s",
			SVC_HTTPS == type ? "https://" : "http://", ip);

	if (CURLE_OK != (err = curl_easy_setopt(http_ctx->easyhandle, opt = CURLOPT_USERAGENT,
			"Zabbix " ZABBIX_VERSION)) ||
			CURLE_OK != (err = curl_easy_setopt(http_ctx->easyhandle, opt = CURLOPT_URL, url)) ||
			CURLE_OK != (err = curl_easy_setopt(http_ctx->easyhandle, opt = CURLOPT_PORT, (long)port)) ||
			CURLE_OK != (err = curl_easy_setopt(http_ctx->easyhandle, opt = CURLOPT_NOBODY, 1L)) ||
			CURLE_OK != (err = curl_easy_setopt(http_ctx->easyhandle, opt = CURLOPT_SSL_VERIFYPEER, 0L)) ||
			CURLE_OK != (err = curl_easy_setopt(http_ctx->easyhandle, opt = CURLOPT_SSL_VERIFYHOST, 0L)) ||
			(NULL != config_source_ip && CURLE_OK != (err = curl_easy_setopt(http_ctx->easyhandle,
					opt = CURLOPT_INTERFACE, config_source_ip))) ||
			CURLE_OK != (err = curl_easy_setopt(http_ctx->easyhandle, opt = CURLOPT_TIMEOUT,
			(long)timeout)) ||
			CURLE_OK != (err = curl_easy_setopt(http_ctx->easyhandle, opt = CURLOPT_ACCEPT_ENCODING,
			"")))
	{
		*error = zbx_dsprintf(*error, "cannot set cURL option [%d]: %s", (int)opt, curl_easy_strerror(err));
		goto fail;
	}

#if LIBCURL_VERSION_NUM >= 0x071304
	/* CURLOPT_PROTOCOLS is supported starting with version 7.19.4 (0x071304) */
	/* CURLOPT_PROTOCOLS was deprecated in favor of CURLOPT_PROTOCOLS_STR starting with version 7.85.0 (0x075500) */
#	if LIBCURL_VERSION_NUM >= 0x075500
	if (CURLE_OK != (err = curl_easy_setopt(http_ctx->easyhandle, CURLOPT_PROTOCOLS_STR, "HTTP,HTTPS")))
#	else
	if (CURLE_OK != (err = curl_easy_setopt(http_ctx->easyhandle, CURLOPT_PROTOCOLS,
			CURLPROTO_HTTP | CURLPROTO_HTTPS)))
#	endif
	{
		*error = zbx_dsprintf(*error, "cannot set allowed protocols: %s", curl_easy_strerror(err));
		goto fail;
	}
#endif

	if (CURLE_OK != (err = curl_easy_setopt(http_ctx->easyhandle, CURLOPT_PRIVATE, http_ctx)))
	{
		*error = zbx_dsprintf(*error, "cannot set pointer to private data: %s", curl_easy_strerror(err));
		goto fail;
	}

	if (CURLM_OK != (merr = curl_multi_add_handle(curl_mhandle, http_ctx->easyhandle)))
	{
		*error = zbx_dsprintf(*error, "cannot add a standard curl handle to the multi stack: %s",
				curl_multi_strerror(merr));
		goto fail;
	}

	return SUCCEED;
fail:
	curl_easy_cleanup(http_ctx->easyhandle);
	return FAIL;
}
#endif