/*
** 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 .
**/
#include "zbxcommon.h"
#ifdef HAVE_LIBCURL
#include "zbxhttp.h"
#include "zbxnum.h"
#include "zbxdbhigh.h"
#include "zbxstr.h"
#include "zbxthreads.h"
#include "zbxjson.h"
#include "zbxcurl.h"
#include
size_t zbx_curl_write_cb(void *ptr, size_t size, size_t nmemb, void *userdata)
{
size_t r_size = size * nmemb;
zbx_http_response_t *response;
response = (zbx_http_response_t*)userdata;
if (ZBX_MAX_RECV_DATA_SIZE < response->offset + r_size)
return 0;
zbx_str_memcpy_alloc(&response->data, &response->allocated, &response->offset, (const char *)ptr, r_size);
return r_size;
}
size_t zbx_curl_ignore_cb(void *ptr, size_t size, size_t nmemb, void *userdata)
{
ZBX_UNUSED(ptr);
ZBX_UNUSED(userdata);
return size * nmemb;
}
int zbx_http_prepare_callbacks(CURL *easyhandle, zbx_http_response_t *header, zbx_http_response_t *body,
zbx_curl_cb_t header_cb, zbx_curl_cb_t body_cb, char *errbuf, char **error)
{
CURLcode err;
if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_HEADERFUNCTION, header_cb)))
{
*error = zbx_dsprintf(*error, "Cannot set header function: %s", curl_easy_strerror(err));
return FAIL;
}
if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_HEADERDATA, header)))
{
*error = zbx_dsprintf(*error, "Cannot set header callback: %s", curl_easy_strerror(err));
return FAIL;
}
if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_WRITEFUNCTION, body_cb)))
{
*error = zbx_dsprintf(*error, "Cannot set write function: %s", curl_easy_strerror(err));
return FAIL;
}
if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_WRITEDATA, body)))
{
*error = zbx_dsprintf(*error, "Cannot set write callback: %s", curl_easy_strerror(err));
return FAIL;
}
if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_ERRORBUFFER, errbuf)))
{
*error = zbx_dsprintf(*error, "Cannot set error buffer: %s", curl_easy_strerror(err));
return FAIL;
}
return SUCCEED;
}
int zbx_http_prepare_ssl(CURL *easyhandle, const char *ssl_cert_file, const char *ssl_key_file,
const char *ssl_key_password, unsigned char verify_peer, unsigned char verify_host,
const char *config_source_ip, const char *config_ssl_ca_location, const char *config_ssl_cert_location,
const char *config_ssl_key_location, char **error)
{
CURLcode err;
if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_SSL_VERIFYPEER, 0 == verify_peer ? 0L : 1L)))
{
*error = zbx_dsprintf(*error, "Cannot set verify the peer's SSL certificate: %s",
curl_easy_strerror(err));
return FAIL;
}
if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_SSL_VERIFYHOST, 0 == verify_host ? 0L : 2L)))
{
*error = zbx_dsprintf(*error, "Cannot set verify the certificate's name against host: %s",
curl_easy_strerror(err));
return FAIL;
}
if (NULL != config_source_ip)
{
if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_INTERFACE, config_source_ip)))
{
*error = zbx_dsprintf(*error, "Cannot specify source interface for outgoing traffic: %s",
curl_easy_strerror(err));
return FAIL;
}
}
if (0 != verify_peer && NULL != config_ssl_ca_location)
{
if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_CAPATH, config_ssl_ca_location)))
{
*error = zbx_dsprintf(*error, "Cannot specify directory holding CA certificates: %s",
curl_easy_strerror(err));
return FAIL;
}
}
if (NULL != ssl_cert_file && '\0' != *ssl_cert_file)
{
char *file_name = zbx_dsprintf(NULL, "%s/%s", config_ssl_cert_location, ssl_cert_file);
zabbix_log(LOG_LEVEL_DEBUG, "using SSL certificate file: '%s'", file_name);
err = curl_easy_setopt(easyhandle, CURLOPT_SSLCERT, file_name);
zbx_free(file_name);
if (CURLE_OK != err)
{
*error = zbx_dsprintf(*error, "Cannot set SSL client certificate: %s", curl_easy_strerror(err));
return FAIL;
}
if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_SSLCERTTYPE, "PEM")))
{
*error = zbx_dsprintf(NULL, "Cannot specify type of the client SSL certificate: %s",
curl_easy_strerror(err));
return FAIL;
}
}
if (NULL != ssl_key_file && '\0' != *ssl_key_file)
{
char *file_name = zbx_dsprintf(NULL, "%s/%s", config_ssl_key_location, ssl_key_file);
zabbix_log(LOG_LEVEL_DEBUG, "using SSL private key file: '%s'", file_name);
err = curl_easy_setopt(easyhandle, CURLOPT_SSLKEY, file_name);
zbx_free(file_name);
if (CURLE_OK != err)
{
*error = zbx_dsprintf(NULL, "Cannot specify private keyfile for TLS and SSL client cert: %s",
curl_easy_strerror(err));
return FAIL;
}
if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_SSLKEYTYPE, "PEM")))
{
*error = zbx_dsprintf(NULL, "Cannot set type of the private key file: %s",
curl_easy_strerror(err));
return FAIL;
}
}
if ('\0' != *ssl_key_password)
{
if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_KEYPASSWD, ssl_key_password)))
{
*error = zbx_dsprintf(NULL, "Cannot set passphrase to private key: %s",
curl_easy_strerror(err));
return FAIL;
}
}
return SUCCEED;
}
int zbx_http_prepare_auth(CURL *easyhandle, unsigned char authtype, const char *username, const char *password,
const char *token, char **error)
{
CURLcode err;
long curlauth = 0;
char auth[MAX_STRING_LEN];
if (HTTPTEST_AUTH_NONE == authtype)
return SUCCEED;
zabbix_log(LOG_LEVEL_DEBUG, "setting HTTPAUTH [%d]", authtype);
switch (authtype)
{
case HTTPTEST_AUTH_BASIC:
curlauth = CURLAUTH_BASIC;
break;
case HTTPTEST_AUTH_NTLM:
curlauth = CURLAUTH_NTLM;
break;
case HTTPTEST_AUTH_NEGOTIATE:
curlauth = CURLAUTH_NEGOTIATE;
break;
case HTTPTEST_AUTH_DIGEST:
curlauth = CURLAUTH_DIGEST;
break;
case HTTPTEST_AUTH_BEARER:
if (SUCCEED != zbx_curl_has_bearer(error))
return FAIL;
curlauth = CURLAUTH_BEARER;
break;
default:
THIS_SHOULD_NEVER_HAPPEN;
break;
}
if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_HTTPAUTH, curlauth)))
{
*error = zbx_dsprintf(*error, "Cannot set HTTP server authentication method: %s",
curl_easy_strerror(err));
return FAIL;
}
switch (authtype)
{
case HTTPTEST_AUTH_BEARER:
if (NULL == token || '\0' == *token)
{
*error = zbx_dsprintf(*error, "cannot set empty bearer token");
return FAIL;
}
if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_XOAUTH2_BEARER, token)))
{
*error = zbx_dsprintf(*error, "Cannot set bearer: %s", curl_easy_strerror(err));
return FAIL;
}
break;
default:
zbx_snprintf(auth, sizeof(auth), "%s:%s", username, password);
if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_USERPWD, auth)))
{
*error = zbx_dsprintf(*error, "Cannot set user name and password: %s",
curl_easy_strerror(err));
return FAIL;
}
break;
}
return SUCCEED;
}
char *zbx_http_parse_header(char **headers)
{
while ('\0' != **headers)
{
char c, *p_end, *line;
while ('\r' == **headers || '\n' == **headers)
(*headers)++;
p_end = *headers;
while ('\0' != *p_end && '\r' != *p_end && '\n' != *p_end)
p_end++;
if (*headers == p_end)
return NULL;
if ('\0' != (c = *p_end))
*p_end = '\0';
line = zbx_strdup(NULL, *headers);
if ('\0' != c)
*p_end = c;
*headers = p_end;
zbx_lrtrim(line, " \t");
if ('\0' == *line)
zbx_free(line);
else
return line;
}
return NULL;
}
int zbx_http_req(const char *url, const char *header, long timeout, const char *ssl_cert_file,
const char *ssl_key_file, const char *config_source_ip, const char *config_ssl_ca_location,
const char *config_ssl_cert_location, const char *config_ssl_key_location, char **out,
const char *post_data, long *response_code, char **error)
{
CURL *easyhandle;
CURLcode err;
char errbuf[CURL_ERROR_SIZE];
int ret = FAIL;
struct curl_slist *headers_slist = NULL;
zbx_http_response_t body = {0}, response_header = {0};
zabbix_log(LOG_LEVEL_DEBUG, "In %s() URL '%s'", __func__, url);
*errbuf = '\0';
if (NULL == (easyhandle = curl_easy_init()))
{
*error = zbx_strdup(NULL, "Cannot initialize cURL library");
goto clean;
}
if (NULL != post_data)
{
if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_POSTFIELDS, post_data)))
{
*error = zbx_dsprintf(*error, "Cannot specify data to POST: %s",
curl_easy_strerror(err));
goto clean;
}
}
if (SUCCEED != zbx_http_prepare_callbacks(easyhandle, &response_header, &body, zbx_curl_ignore_cb,
zbx_curl_write_cb, errbuf, error))
{
goto clean;
}
if (SUCCEED != zbx_http_prepare_ssl(easyhandle, ssl_cert_file, ssl_key_file, "", 1, 1, config_source_ip,
config_ssl_ca_location, config_ssl_cert_location, config_ssl_key_location, error))
{
goto clean;
}
if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_USERAGENT, "Zabbix " ZABBIX_VERSION)))
{
*error = zbx_dsprintf(NULL, "Cannot set user agent: %s", curl_easy_strerror(err));
goto clean;
}
if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_PROXY, "")))
{
*error = zbx_dsprintf(NULL, "Cannot set proxy: %s", curl_easy_strerror(err));
goto clean;
}
if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_TIMEOUT, timeout)))
{
*error = zbx_dsprintf(NULL, "Cannot specify timeout: %s", curl_easy_strerror(err));
goto clean;
}
if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_HTTPHEADER,
(headers_slist = curl_slist_append(headers_slist, header)))))
{
*error = zbx_dsprintf(NULL, "Cannot specify headers: %s", curl_easy_strerror(err));
goto clean;
}
if (SUCCEED != zbx_curl_setopt_https(easyhandle, error))
goto clean;
if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_URL, url)))
{
*error = zbx_dsprintf(NULL, "Cannot specify URL: %s", curl_easy_strerror(err));
goto clean;
}
if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_ACCEPT_ENCODING, "")))
{
*error = zbx_dsprintf(NULL, "Cannot set cURL encoding option: %s", curl_easy_strerror(err));
goto clean;
}
if (CURLE_OK != (err = curl_easy_perform(easyhandle)))
{
*error = zbx_dsprintf(NULL, "Cannot perform request: %s", '\0' == *errbuf ? curl_easy_strerror(err) :
errbuf);
goto clean;
}
if (CURLE_OK != (err = curl_easy_getinfo(easyhandle, CURLINFO_RESPONSE_CODE, response_code)))
{
*error = zbx_dsprintf(NULL, "Cannot get the response code: %s", curl_easy_strerror(err));
goto clean;
}
if (NULL != body.data)
{
*out = body.data;
body.data = NULL;
}
else
*out = zbx_strdup(NULL, "");
ret = SUCCEED;
clean:
curl_slist_free_all(headers_slist); /* must be called after curl_easy_perform() */
curl_easy_cleanup(easyhandle);
zbx_free(body.data);
zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));
return ret;
}
static const char *zbx_request_string(int result)
{
switch (result)
{
case HTTP_REQUEST_GET:
return "GET";
case HTTP_REQUEST_POST:
return "POST";
case HTTP_REQUEST_PUT:
return "PUT";
case HTTP_REQUEST_HEAD:
return "HEAD";
default:
return "unknown";
}
}
static int http_prepare_request(CURL *easyhandle, const char *posts, unsigned char request_method, char **error)
{
CURLcode err;
switch (request_method)
{
case HTTP_REQUEST_POST:
if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_POSTFIELDS, posts)))
{
*error = zbx_dsprintf(*error, "Cannot specify data to POST: %s",
curl_easy_strerror(err));
return FAIL;
}
break;
case HTTP_REQUEST_GET:
if ('\0' == *posts)
return SUCCEED;
if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_POSTFIELDS, posts)))
{
*error = zbx_dsprintf(*error, "Cannot specify data to POST: %s",
curl_easy_strerror(err));
return FAIL;
}
if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_CUSTOMREQUEST, "GET")))
{
*error = zbx_dsprintf(*error, "Cannot specify custom GET request: %s",
curl_easy_strerror(err));
return FAIL;
}
break;
case HTTP_REQUEST_HEAD:
if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_NOBODY, 1L)))
{
*error = zbx_dsprintf(*error, "Cannot specify HEAD request: %s",
curl_easy_strerror(err));
return FAIL;
}
break;
case HTTP_REQUEST_PUT:
if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_POSTFIELDS, posts)))
{
*error = zbx_dsprintf(*error, "Cannot specify data to POST: %s",
curl_easy_strerror(err));
return FAIL;
}
if (CURLE_OK != (err = curl_easy_setopt(easyhandle, CURLOPT_CUSTOMREQUEST, "PUT")))
{
*error = zbx_dsprintf(*error, "Cannot specify custom GET request: %s",
curl_easy_strerror(err));
return FAIL;
}
break;
default:
THIS_SHOULD_NEVER_HAPPEN;
*error = zbx_strdup(*error, "Unsupported request method");
return FAIL;
}
return SUCCEED;
}
static void http_add_json_header(struct zbx_json *json, char *line)
{
char *colon;
if (NULL != (colon = strchr(line, ':')))
{
zbx_ltrim(colon + 1, " \t");
*colon = '\0';
zbx_json_addstring(json, line, colon + 1, ZBX_JSON_TYPE_STRING);
*colon = ':';
}
else
zbx_json_addstring(json, line, "", ZBX_JSON_TYPE_STRING);
}
static void http_output_json(unsigned char retrieve_mode, char **buffer, zbx_http_response_t *header,
zbx_http_response_t *body)
{
struct zbx_json json;
struct zbx_json_parse jp;
char *headers, *line;
unsigned char json_content = 0;
zbx_json_init(&json, ZBX_JSON_STAT_BUF_LEN);
headers = header->data;
if (retrieve_mode != ZBX_RETRIEVE_MODE_CONTENT)
zbx_json_addobject(&json, "header");
while (NULL != (line = zbx_http_parse_header(&headers)))
{
if (0 == json_content &&
0 == zbx_strncasecmp(line, "Content-Type:", ZBX_CONST_STRLEN("Content-Type:")) &&
NULL != strstr(line, "application/json"))
{
json_content = 1;
}
if (retrieve_mode != ZBX_RETRIEVE_MODE_CONTENT)
http_add_json_header(&json, line);
zbx_free(line);
}
if (retrieve_mode != ZBX_RETRIEVE_MODE_CONTENT)
zbx_json_close(&json);
if (NULL != body->data)
{
if (0 == json_content)
{
zbx_json_addstring(&json, "body", body->data, ZBX_JSON_TYPE_STRING);
}
else if (FAIL == zbx_json_open(body->data, &jp))
{
zbx_json_addstring(&json, "body", body->data, ZBX_JSON_TYPE_STRING);
zabbix_log(LOG_LEVEL_DEBUG, "received invalid JSON object %s", zbx_json_strerror());
}
else
{
zbx_lrtrim(body->data, ZBX_WHITESPACE);
zbx_json_addraw(&json, "body", body->data);
}
}
*buffer = zbx_strdup(NULL, json.buffer);
zbx_json_free(&json);
}
CURLcode zbx_http_request_sync_perform(CURL *easyhandle, zbx_http_context_t *context, int attempt_interval,
int check_response_code)
{
CURLcode err;
char status_codes[] = "200,201,202,203,204,400,401,403,404,405,415,422";
long response_code;
/* try to retrieve page several times depending on number of retries */
do
{
*context->errbuf = '\0';
if (CURLE_OK == (err = curl_easy_perform(easyhandle)))
{
if (ZBX_HTTP_CHECK_RESPONSE_CODE == check_response_code)
{
if (CURLE_OK != (err = curl_easy_getinfo(easyhandle, CURLINFO_RESPONSE_CODE,
&response_code)))
{
zabbix_log(LOG_LEVEL_INFORMATION, "cannot get the response code: %s",
curl_easy_strerror(err));
goto next_attempt;
}
else if (FAIL == zbx_int_in_list(status_codes, (int)response_code))
goto next_attempt;
return err;
}
return err;
}
else
{
if (1 != context->max_attempts)
{
zabbix_log(LOG_LEVEL_INFORMATION, "cannot perform request: %s",
'\0' == *context->errbuf ? curl_easy_strerror(err) : context->errbuf);
}
}
next_attempt:
context->header.offset = 0;
context->body.offset = 0;
if (0 != attempt_interval && 1 < context->max_attempts)
zbx_sleep((unsigned int)attempt_interval);
}
while (0 < --context->max_attempts);
return err;
}
int zbx_http_handle_response(CURL *easyhandle, zbx_http_context_t *context, CURLcode err, long *response_code,
char **out, char **error)
{
if (CURLE_OK != err)
{
if (CURLE_WRITE_ERROR == err)
{
*error = zbx_strdup(NULL, "The requested value is too large");
}
else
{
*error = zbx_dsprintf(NULL, "Cannot perform request: %s",
'\0' == *context->errbuf ? curl_easy_strerror(err) : context->errbuf);
}
return FAIL;
}
if (CURLE_OK != (err = curl_easy_getinfo(easyhandle, CURLINFO_RESPONSE_CODE, response_code)))
{
*error = zbx_dsprintf(NULL, "Cannot get the response code: %s", curl_easy_strerror(err));
return FAIL;
}
if (NULL == context->header.data)
{
*error = zbx_dsprintf(NULL, "Server returned empty header");
return FAIL;
}
switch (context->retrieve_mode)
{
case ZBX_RETRIEVE_MODE_CONTENT:
zbx_http_convert_to_utf8(easyhandle, &context->body.data, &context->body.offset,
&context->body.allocated);
if (HTTP_STORE_JSON == context->output_format)
{
http_output_json(context->retrieve_mode, out, &context->header, &context->body);
}
else
{
if (NULL != context->body.data)
{
*out = context->body.data;
context->body.data = NULL;
}
else
*out = zbx_strdup(NULL, "");
}
break;
case ZBX_RETRIEVE_MODE_HEADERS:
zbx_replace_invalid_utf8(context->header.data);
if (HTTP_STORE_JSON == context->output_format)
{
char *line;
struct zbx_json json;
zbx_json_init(&json, ZBX_JSON_STAT_BUF_LEN);
zbx_json_addobject(&json, "header");
char *headers_ptr = context->header.data;
while (NULL != (line = zbx_http_parse_header(&headers_ptr)))
{
http_add_json_header(&json, line);
zbx_free(line);
}
*out = zbx_strdup(NULL, json.buffer);
zbx_json_free(&json);
}
else
{
*out = context->header.data;
context->header.data = NULL;
}
break;
case ZBX_RETRIEVE_MODE_BOTH:
zbx_replace_invalid_utf8(context->header.data);
zbx_http_convert_to_utf8(easyhandle, &context->body.data, &context->body.offset,
&context->body.allocated);
if (HTTP_STORE_JSON == context->output_format)
{
http_output_json(context->retrieve_mode, out, &context->header, &context->body);
}
else
{
if (NULL != context->body.data)
{
zbx_strncpy_alloc(&context->header.data, &context->header.allocated,
&context->header.offset, context->body.data,
context->body.offset);
}
*out = context->header.data;
context->header.data = NULL;
}
break;
default:
*error = zbx_dsprintf(NULL, "invalid retrieve mode");
return FAIL;
}
return SUCCEED;
}
int zbx_handle_response_code(char *status_codes, long response_code, const char *out, char **error)
{
if ('\0' != *status_codes && FAIL == zbx_int_in_list(status_codes, (int)response_code))
{
if (NULL != out)
{
*error = zbx_dsprintf(NULL, "Response code \"%ld\" did not match any of the required status"
" codes \"%s\"\n%s", response_code, status_codes, out);
}
else
{
*error = zbx_dsprintf(NULL, "Response code \"%ld\" did not match any of the required status"
" codes \"%s\"", response_code, status_codes);
}
return FAIL;
}
return SUCCEED;
}
void zbx_http_context_create(zbx_http_context_t *context)
{
memset(context, 0, sizeof(zbx_http_context_t));
}
void zbx_http_context_destroy(zbx_http_context_t *context)
{
curl_slist_free_all(context->headers_slist); /* must be called after curl_easy_perform() */
zbx_free(context->body.data);
zbx_free(context->header.data);
curl_easy_cleanup(context->easyhandle);
}
int zbx_http_request_prepare(zbx_http_context_t *context, unsigned char request_method, const char *url,
const char *query_fields, char *headers, const char *posts, unsigned char retrieve_mode,
const char *http_proxy, unsigned char follow_redirects,
int timeout, int max_attempts, const char *ssl_cert_file, const char *ssl_key_file,
const char *ssl_key_password, unsigned char verify_peer, unsigned char verify_host,
unsigned char authtype, const char *username, const char *password, const char *token,
unsigned char post_type, unsigned char output_format, const char *config_source_ip,
const char *config_ssl_ca_location, const char *config_ssl_cert_location,
const char *config_ssl_key_location, char **error)
{
CURLcode err;
char url_buffer[ZBX_ITEM_URL_LEN_MAX], *headers_ptr, *line;
int ret = NOTSUPPORTED, found = FAIL;
zbx_curl_cb_t curl_body_cb;
char application_json[] = {"Content-Type: application/json"};
char application_ndjson[] = {"Content-Type: application/x-ndjson"};
char application_xml[] = {"Content-Type: application/xml"};
zabbix_log(LOG_LEVEL_DEBUG, "In %s() request method '%s' URL '%s%s' headers '%s'",
__func__, zbx_request_string(request_method), url, query_fields, headers);
zabbix_log(LOG_LEVEL_TRACE, "message body '%s'", posts);
context->max_attempts = max_attempts;
context->output_format = output_format;
context->retrieve_mode = retrieve_mode;
if (NULL == (context->easyhandle = curl_easy_init()))
{
*error = zbx_strdup(NULL, "Cannot initialize cURL library");
goto clean;
}
switch (retrieve_mode)
{
case ZBX_RETRIEVE_MODE_CONTENT:
case ZBX_RETRIEVE_MODE_BOTH:
curl_body_cb = zbx_curl_write_cb;
break;
case ZBX_RETRIEVE_MODE_HEADERS:
curl_body_cb = zbx_curl_ignore_cb;
break;
default:
THIS_SHOULD_NEVER_HAPPEN;
*error = zbx_strdup(NULL, "Invalid retrieve mode");
goto clean;
}
if (SUCCEED != zbx_http_prepare_callbacks(context->easyhandle, &context->header, &context->body,
zbx_curl_write_cb, curl_body_cb, context->errbuf, error))
{
goto clean;
}
if (CURLE_OK != (err = curl_easy_setopt(context->easyhandle, CURLOPT_PROXY, http_proxy)))
{
*error = zbx_dsprintf(NULL, "Cannot set proxy: %s", curl_easy_strerror(err));
goto clean;
}
if (CURLE_OK != (err = curl_easy_setopt(context->easyhandle, CURLOPT_FOLLOWLOCATION,
0 == follow_redirects ? 0L : 1L)))
{
*error = zbx_dsprintf(NULL, "Cannot set follow redirects: %s", curl_easy_strerror(err));
goto clean;
}
if (0 != follow_redirects &&
CURLE_OK != (err = curl_easy_setopt(context->easyhandle, CURLOPT_MAXREDIRS,
ZBX_CURLOPT_MAXREDIRS)))
{
*error = zbx_dsprintf(NULL, "Cannot set number of redirects allowed: %s", curl_easy_strerror(err));
goto clean;
}
if (CURLE_OK != (err = curl_easy_setopt(context->easyhandle, CURLOPT_TIMEOUT, (long)timeout)))
{
*error = zbx_dsprintf(NULL, "Cannot specify timeout: %s", curl_easy_strerror(err));
goto clean;
}
if (SUCCEED != zbx_http_prepare_ssl(context->easyhandle, ssl_cert_file, ssl_key_file, ssl_key_password,
verify_peer, verify_host, config_source_ip, config_ssl_ca_location, config_ssl_cert_location,
config_ssl_key_location, error))
{
goto clean;
}
if (SUCCEED != zbx_http_prepare_auth(context->easyhandle, authtype, username, password, token, error))
goto clean;
if (SUCCEED != http_prepare_request(context->easyhandle, posts, request_method, error))
{
goto clean;
}
headers_ptr = headers;
while (NULL != (line = zbx_http_parse_header(&headers_ptr)))
{
context->headers_slist = curl_slist_append(context->headers_slist, line);
if (FAIL == found && 0 == strncmp(line, "Content-Type:", ZBX_CONST_STRLEN("Content-Type:")))
found = SUCCEED;
zbx_free(line);
}
if (FAIL == found)
{
if (ZBX_POSTTYPE_JSON == post_type)
context->headers_slist = curl_slist_append(context->headers_slist, application_json);
else if (ZBX_POSTTYPE_XML == post_type)
context->headers_slist = curl_slist_append(context->headers_slist, application_xml);
else if (ZBX_POSTTYPE_NDJSON == post_type)
context->headers_slist = curl_slist_append(context->headers_slist, application_ndjson);
}
if (CURLE_OK != (err = curl_easy_setopt(context->easyhandle, CURLOPT_HTTPHEADER, context->headers_slist)))
{
*error = zbx_dsprintf(NULL, "Cannot specify headers: %s", curl_easy_strerror(err));
goto clean;
}
if (SUCCEED != zbx_curl_setopt_https(context->easyhandle, error))
goto clean;
zbx_snprintf(url_buffer, sizeof(url_buffer),"%s%s", url, query_fields);
if (CURLE_OK != (err = curl_easy_setopt(context->easyhandle, CURLOPT_URL, url_buffer)))
{
*error = zbx_dsprintf(NULL, "Cannot specify URL: %s", curl_easy_strerror(err));
goto clean;
}
if (CURLE_OK != (err = curl_easy_setopt(context->easyhandle, CURLOPT_ACCEPT_ENCODING, "")))
{
*error = zbx_dsprintf(NULL, "Cannot set cURL encoding option: %s", curl_easy_strerror(err));
goto clean;
}
if (CURLE_OK != (err = curl_easy_setopt(context->easyhandle, CURLOPT_COOKIEFILE, "")))
{
*error = zbx_dsprintf(NULL, "Cannot enable cURL cookie engine: %s", curl_easy_strerror(err));
goto clean;
}
ret = SUCCEED;
clean:
zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));
return ret;
}
void zbx_http_convert_to_utf8(CURL *easyhandle, char **body, size_t *size, size_t *allocated)
{
char *charset;
const char *content_type;
if (NULL == *body)
return;
content_type = zbx_curl_content_type(easyhandle);
charset = zbx_determine_charset(content_type, *body, *size);
if (0 != strcmp(charset, "UTF-8"))
{
char *converted, *error = NULL;
zabbix_log(LOG_LEVEL_DEBUG, "converting from charset '%s'", charset);
if (NULL == (converted = zbx_convert_to_utf8(*body, *size, charset, &error)))
{
zabbix_log(LOG_LEVEL_DEBUG, "cannot convert from charset '%s': %s", charset, error);
zbx_free(error);
}
else
{
zbx_free(*body);
*body = converted;
*size = strlen(converted);
*allocated = *size;
}
}
zbx_free(charset);
zbx_replace_invalid_utf8(*body);
}
#endif