1 /*
2 ** Zabbix
3 ** Copyright (C) 2001-2021 Zabbix SIA
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 **/
19 
20 #include "common.h"
21 #include "db.h"
22 #include "log.h"
23 #include "zbxregexp.h"
24 #include "zbxhttp.h"
25 
26 #include "httpmacro.h"
27 
28 #define REGEXP_PREFIX		"regex:"
29 #define REGEXP_PREFIX_SIZE	ZBX_CONST_STRLEN(REGEXP_PREFIX)
30 
31 /******************************************************************************
32  *                                                                            *
33  * Function: httpmacro_cmp_func                                               *
34  *                                                                            *
35  * Purpose: compare two macros by name                                        *
36  *                                                                            *
37  * Parameters: d1 - [IN] the first macro                                      *
38  *             d2 - [IN] the second macro                                     *
39  *                                                                            *
40  * Return value: <0 - the first macro name is 'less' than second              *
41  *                0 - the macro names are equal                               *
42  *               >0 - the first macro name is 'greater' than second           *
43  *                                                                            *
44  * Author: Andris Zeila                                                       *
45  *                                                                            *
46  ******************************************************************************/
httpmacro_cmp_func(const void * d1,const void * d2)47 static int 	httpmacro_cmp_func(const void *d1, const void *d2)
48 {
49 	const zbx_ptr_pair_t	*pair1 = (const zbx_ptr_pair_t *)d1;
50 	const zbx_ptr_pair_t	*pair2 = (const zbx_ptr_pair_t *)d2;
51 
52 	return strcmp((char *)pair1->first, (char *)pair2->first);
53 }
54 
55 /******************************************************************************
56  *                                                                            *
57  * Function: httpmacro_append_pair                                            *
58  *                                                                            *
59  * Purpose: appends key/value pair to the http test macro cache.              *
60  *          If the value format is 'regex:<pattern>', then regular expression *
61  *          match is performed against the supplied data value and specified  *
62  *          pattern. The first captured group is assigned to the macro value. *
63  *                                                                            *
64  * Parameters: httptest - [IN/OUT] the http test data                         *
65  *             pkey     - [IN] a pointer to the macro name (key) data         *
66  *             nkey     - [IN] the macro name (key) size                      *
67  *             pvalue   - [IN] a pointer to the macro value data              *
68  *             nvalue   - [IN] the value size                                 *
69  *             data     - [IN] the data for regexp matching (optional)        *
70  *             err_str  - [OUT] the error message (optional)                  *
71  *                                                                            *
72  * Return value:  SUCCEED - the key/value pair was added successfully         *
73  *                   FAIL - key/value pair adding to cache failed.            *
74  *                          The failure reason can be either empty key/value, *
75  *                          wrong key format or failed regular expression     *
76  *                          match.                                            *
77  *                                                                            *
78  * Author: Andris Zeila                                                       *
79  *                                                                            *
80  ******************************************************************************/
httpmacro_append_pair(zbx_httptest_t * httptest,const char * pkey,size_t nkey,const char * pvalue,size_t nvalue,const char * data,char ** err_str)81 static int	httpmacro_append_pair(zbx_httptest_t *httptest, const char *pkey, size_t nkey,
82 			const char *pvalue, size_t nvalue, const char *data, char **err_str)
83 {
84 	const char	*__function_name = "httpmacro_append_pair";
85 	char 		*value_str = NULL;
86 	size_t		key_size = 0, key_offset = 0, value_size = 0, value_offset = 0;
87 	zbx_ptr_pair_t	pair = {NULL, NULL};
88 	int		index, ret = FAIL;
89 
90 	zabbix_log(LOG_LEVEL_DEBUG, "In %s() pkey:'%.*s' pvalue:'%.*s'",
91 			__function_name, (int)nkey, pkey, (int)nvalue, pvalue);
92 
93 	if (0 == nkey && 0 != nvalue)
94 	{
95 		zabbix_log(LOG_LEVEL_DEBUG, "%s() missing variable name (only value provided): \"%.*s\"",
96 				__function_name, (int)nvalue, pvalue);
97 
98 		if (NULL != err_str && NULL == *err_str)
99 		{
100 			*err_str = zbx_dsprintf(*err_str, "missing variable name (only value provided):"
101 					" \"%.*s\"", (int)nvalue, pvalue);
102 		}
103 
104 		goto out;
105 	}
106 
107 	if ('{' != pkey[0] || '}' != pkey[nkey - 1])
108 	{
109 		zabbix_log(LOG_LEVEL_DEBUG, "%s() \"%.*s\" not enclosed in {}", __function_name, (int)nkey, pkey);
110 
111 		if (NULL != err_str && NULL == *err_str)
112 			*err_str = zbx_dsprintf(*err_str, "\"%.*s\" not enclosed in {}", (int)nkey, pkey);
113 
114 		goto out;
115 	}
116 
117 	/* get macro value */
118 	zbx_strncpy_alloc(&value_str, &value_size, &value_offset, pvalue, nvalue);
119 	if (0 == strncmp(REGEXP_PREFIX, value_str, REGEXP_PREFIX_SIZE))
120 	{
121 		int	rc;
122 		/* The value contains regexp pattern, retrieve the first captured group or fail.  */
123 		/* The \@ sequence is a special construct to fail if the pattern matches but does */
124 		/* not contain groups to capture.                                                 */
125 
126 		rc = zbx_mregexp_sub(data, value_str + REGEXP_PREFIX_SIZE, "\\@", (char **)&pair.second);
127 		zbx_free(value_str);
128 
129 		if (SUCCEED != rc || NULL == pair.second)
130 		{
131 			zabbix_log(LOG_LEVEL_DEBUG, "%s() cannot extract the value of \"%.*s\" from response",
132 					__function_name, (int)nkey, pkey);
133 
134 			if (NULL != err_str && NULL == *err_str)
135 			{
136 				*err_str = zbx_dsprintf(*err_str, "cannot extract the value of \"%.*s\""
137 						" from response", (int)nkey, pkey);
138 			}
139 
140 			goto out;
141 		}
142 	}
143 	else
144 		pair.second = value_str;
145 
146 	/* get macro name */
147 	zbx_strncpy_alloc((char **)&pair.first, &key_size, &key_offset, pkey, nkey);
148 
149 	/* remove existing macro if necessary */
150 	index = zbx_vector_ptr_pair_search(&httptest->macros, pair, httpmacro_cmp_func);
151 	if (FAIL != index)
152 	{
153 		zbx_ptr_pair_t	*ppair = &httptest->macros.values[index];
154 
155 		zbx_free(ppair->first);
156 		zbx_free(ppair->second);
157 		zbx_vector_ptr_pair_remove_noorder(&httptest->macros, index);
158 	}
159 	zbx_vector_ptr_pair_append(&httptest->macros, pair);
160 
161 	zabbix_log(LOG_LEVEL_DEBUG, "append macro '%s'='%s' in cache", (char *)pair.first, (char *)pair.second);
162 
163 	ret = SUCCEED;
164 out:
165 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __function_name, zbx_result_string(ret));
166 
167 	return ret;
168 }
169 
170 /******************************************************************************
171  *                                                                            *
172  * Function: http_substitute_variables                                        *
173  *                                                                            *
174  * Purpose: substitute variables in input string with their values from http  *
175  *          test config                                                       *
176  *                                                                            *
177  * Parameters: httptest - [IN]     the http test data                         *
178  *             data     - [IN/OUT] string to substitute macros in             *
179  *                                                                            *
180  * Author: Alexei Vladishev, Andris Zeila                                     *
181  *                                                                            *
182  ******************************************************************************/
http_substitute_variables(const zbx_httptest_t * httptest,char ** data)183 int	http_substitute_variables(const zbx_httptest_t *httptest, char **data)
184 {
185 	const char	*__function_name = "http_substitute_variables";
186 	char		replace_char, *substitute;
187 	size_t		left, right, len, offset;
188 	int		index, ret = SUCCEED;
189 	zbx_ptr_pair_t	pair = {NULL, NULL};
190 
191 	zabbix_log(LOG_LEVEL_DEBUG, "In %s() data:'%s'", __function_name, *data);
192 
193 	for (left = 0; '\0' != (*data)[left]; left++)
194 	{
195 		if ('{' != (*data)[left])
196 			continue;
197 
198 		offset = ('{' == (*data)[left + 1] ? 1 : 0);
199 
200 		for (right = left + 1; '\0' != (*data)[right] && '}' != (*data)[right]; right++)
201 			;
202 
203 		if ('}' != (*data)[right])
204 			break;
205 
206 		replace_char = (*data)[right + 1];
207 		(*data)[right + 1] = '\0';
208 
209 		pair.first = *data + left + offset;
210 		index = zbx_vector_ptr_pair_search(&httptest->macros, pair, httpmacro_cmp_func);
211 
212 		(*data)[right + 1] = replace_char;
213 
214 		if (FAIL == index)
215 			continue;
216 
217 		substitute = (char *)httptest->macros.values[index].second;
218 
219 		if ('.' == replace_char && 1 == offset)
220 		{
221 			right += 2;
222 			offset = right;
223 
224 			for (; '\0' != (*data)[right] && '}' != (*data)[right]; right++)
225 				;
226 
227 			if ('}' != (*data)[right])
228 				break;
229 
230 			len = right - offset;
231 
232 			if (ZBX_CONST_STRLEN("urlencode()") == len && 0 == strncmp(*data + offset, "urlencode()", len))
233 			{
234 				/* http_variable_urlencode cannot fail (except for "out of memory") */
235 				/* so no check is needed */
236 				substitute = NULL;
237 				zbx_http_url_encode((char *)httptest->macros.values[index].second, &substitute);
238 			}
239 			else if (ZBX_CONST_STRLEN("urldecode()") == len &&
240 					0 == strncmp(*data + offset, "urldecode()", len))
241 			{
242 				/* on error substitute will remain unchanged */
243 				substitute = NULL;
244 				if (FAIL == (ret = zbx_http_url_decode((char *)httptest->macros.values[index].second,
245 						&substitute)))
246 				{
247 					break;
248 				}
249 			}
250 			else
251 				continue;
252 
253 		}
254 		else
255 			left += offset;
256 
257 		zbx_replace_string(data, left, &right, substitute);
258 		if (substitute != (char *)httptest->macros.values[index].second)
259 			zbx_free(substitute);
260 
261 		left = right;
262 	}
263 
264 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s() data:'%s'", __function_name, *data);
265 
266 	return ret;
267 }
268 
269 /******************************************************************************
270  *                                                                            *
271  * Function: http_process_variables                                           *
272  *                                                                            *
273  * Purpose: parses http test/step variable string and stores results into     *
274  *          httptest macro cache.                                             *
275  *          The variables are specified as {<key>}=><value> pairs             *
276  *          If the value format is 'regex:<pattern>', then regular expression *
277  *          match is performed against the supplied data value and specified  *
278  *          pattern. The first captured group is assigned to the macro value. *
279  *                                                                            *
280  * Parameters: httptest  - [IN/OUT] the http test data                        *
281  *             variables - [IN] the variable vector                           *
282  *             data      - [IN] the data for variable regexp matching         *
283  *                         (optional).                                        *
284  *             err_str   - [OUT] the error message (optional)                 *
285  *                                                                            *
286  * Return value: SUCCEED - the variables were processed successfully          *
287  *               FAIL    - the variable processing failed (regexp match       *
288  *                         failed).                                           *
289  *                                                                            *
290  * Author: Andris Zeila                                                       *
291  *                                                                            *
292  ******************************************************************************/
http_process_variables(zbx_httptest_t * httptest,zbx_vector_ptr_pair_t * variables,const char * data,char ** err_str)293 int	http_process_variables(zbx_httptest_t *httptest, zbx_vector_ptr_pair_t *variables, const char *data,
294 		char **err_str)
295 {
296 	const char	*__function_name = "http_process_variables";
297 	char		*key, *value;
298 	int		i, ret = FAIL;
299 
300 	zabbix_log(LOG_LEVEL_DEBUG, "In %s() %d variables", __function_name, variables->values_num);
301 
302 	for (i = 0; i < variables->values_num; i++)
303 	{
304 		key = (char *)variables->values[i].first;
305 		value = (char *)variables->values[i].second;
306 		if (FAIL == httpmacro_append_pair(httptest, key, strlen(key), value, strlen(value), data, err_str))
307 			goto out;
308 	}
309 
310 	ret = SUCCEED;
311 out:
312 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __function_name, zbx_result_string(ret));
313 
314 	return ret;
315 }
316