1<?php
2/*
3** Zabbix
4** Copyright (C) 2001-2021 Zabbix SIA
5**
6** This program is free software; you can redistribute it and/or modify
7** it under the terms of the GNU General Public License as published by
8** the Free Software Foundation; either version 2 of the License, or
9** (at your option) any later version.
10**
11** This program is distributed in the hope that it will be useful,
12** but WITHOUT ANY WARRANTY; without even the implied warranty of
13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14** GNU General Public License for more details.
15**
16** You should have received a copy of the GNU General Public License
17** along with this program; if not, write to the Free Software
18** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19**/
20
21
22/**
23 * Class containing methods for operations with http tests.
24 */
25class CHttpTest extends CApiService {
26
27	public const ACCESS_RULES = [
28		'get' => ['min_user_type' => USER_TYPE_ZABBIX_USER],
29		'create' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN],
30		'update' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN],
31		'delete' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN]
32	];
33
34	protected $tableName = 'httptest';
35	protected $tableAlias = 'ht';
36	protected $sortColumns = ['httptestid', 'name'];
37
38	/**
39	 * Get data about web scenarios.
40	 *
41	 * @param array $options
42	 *
43	 * @return array
44	 */
45	public function get($options = []) {
46		$result = [];
47
48		$sqlParts = [
49			'select'	=> ['httptests' => 'ht.httptestid'],
50			'from'		=> ['httptest' => 'httptest ht'],
51			'where'		=> [],
52			'group'		=> [],
53			'order'		=> [],
54			'limit'		=> null
55		];
56
57		$defOptions = [
58			'httptestids'    => null,
59			'hostids'        => null,
60			'groupids'       => null,
61			'templateids'    => null,
62			'editable'       => false,
63			'inherited'      => null,
64			'templated'      => null,
65			'monitored'      => null,
66			'nopermissions'  => null,
67			'evaltype'		=> TAG_EVAL_TYPE_AND_OR,
68			'tags'			=> null,
69			// filter
70			'filter'         => null,
71			'search'         => null,
72			'searchByAny'    => null,
73			'startSearch'    => false,
74			'excludeSearch'  => false,
75			// output
76			'output'         => API_OUTPUT_EXTEND,
77			'expandName'     => null,
78			'expandStepName' => null,
79			'selectHosts'    => null,
80			'selectSteps'    => null,
81			'selectTags'	 => null,
82			'countOutput'    => false,
83			'groupCount'     => false,
84			'preservekeys'   => false,
85			'sortfield'      => '',
86			'sortorder'      => '',
87			'limit'          => null
88		];
89		$options = zbx_array_merge($defOptions, $options);
90
91		// editable + PERMISSION CHECK
92		if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN && !$options['nopermissions']) {
93			$permission = $options['editable'] ? PERM_READ_WRITE : PERM_READ;
94			$userGroups = getUserGroupsByUserId(self::$userData['userid']);
95
96			$sqlParts['where'][] = 'EXISTS ('.
97					'SELECT NULL'.
98					' FROM hosts_groups hgg'.
99						' JOIN rights r'.
100							' ON r.id=hgg.groupid'.
101								' AND '.dbConditionInt('r.groupid', $userGroups).
102					' WHERE ht.hostid=hgg.hostid'.
103					' GROUP BY hgg.hostid'.
104					' HAVING MIN(r.permission)>'.PERM_DENY.
105						' AND MAX(r.permission)>='.zbx_dbstr($permission).
106					')';
107		}
108
109		// httptestids
110		if (!is_null($options['httptestids'])) {
111			zbx_value2array($options['httptestids']);
112
113			$sqlParts['where']['httptestid'] = dbConditionInt('ht.httptestid', $options['httptestids']);
114		}
115
116		// templateids
117		if (!is_null($options['templateids'])) {
118			zbx_value2array($options['templateids']);
119
120			if (!is_null($options['hostids'])) {
121				zbx_value2array($options['hostids']);
122				$options['hostids'] = array_merge($options['hostids'], $options['templateids']);
123			}
124			else {
125				$options['hostids'] = $options['templateids'];
126			}
127		}
128		// hostids
129		if (!is_null($options['hostids'])) {
130			zbx_value2array($options['hostids']);
131
132			$sqlParts['where']['hostid'] = dbConditionInt('ht.hostid', $options['hostids']);
133
134			if ($options['groupCount']) {
135				$sqlParts['group']['hostid'] = 'ht.hostid';
136			}
137		}
138
139		// tags
140		if ($options['tags'] !== null && $options['tags']) {
141			$sqlParts['where'][] = CApiTagHelper::addWhereCondition($options['tags'], $options['evaltype'], 'ht',
142				'httptest_tag', 'httptestid'
143			);
144		}
145
146		// groupids
147		if (!is_null($options['groupids'])) {
148			zbx_value2array($options['groupids']);
149
150			$sqlParts['from']['hosts_groups'] = 'hosts_groups hg';
151			$sqlParts['where'][] = dbConditionInt('hg.groupid', $options['groupids']);
152			$sqlParts['where'][] = 'hg.hostid=ht.hostid';
153
154			if ($options['groupCount']) {
155				$sqlParts['group']['hg'] = 'hg.groupid';
156			}
157		}
158
159		// inherited
160		if (isset($options['inherited'])) {
161			$sqlParts['where'][] = $options['inherited'] ? 'ht.templateid IS NOT NULL' : 'ht.templateid IS NULL';
162		}
163
164		// templated
165		if (isset($options['templated'])) {
166			$sqlParts['from']['hosts'] = 'hosts h';
167			$sqlParts['where']['ha'] = 'h.hostid=ht.hostid';
168			if ($options['templated']) {
169				$sqlParts['where'][] = 'h.status='.HOST_STATUS_TEMPLATE;
170			}
171			else {
172				$sqlParts['where'][] = 'h.status<>'.HOST_STATUS_TEMPLATE;
173			}
174		}
175
176		// monitored
177		if (!is_null($options['monitored'])) {
178			$sqlParts['from']['hosts'] = 'hosts h';
179			$sqlParts['where']['hht'] = 'h.hostid=ht.hostid';
180
181			if ($options['monitored']) {
182				$sqlParts['where'][] = 'h.status='.HOST_STATUS_MONITORED;
183				$sqlParts['where'][] = 'ht.status='.ITEM_STATUS_ACTIVE;
184			}
185			else {
186				$sqlParts['where'][] = '(h.status<>'.HOST_STATUS_MONITORED.' OR ht.status<>'.ITEM_STATUS_ACTIVE.')';
187			}
188		}
189
190		// search
191		if (is_array($options['search'])) {
192			zbx_db_search('httptest ht', $options, $sqlParts);
193		}
194
195		// filter
196		if (is_array($options['filter'])) {
197			if (array_key_exists('delay', $options['filter']) && $options['filter']['delay'] !== null) {
198				$options['filter']['delay'] = getTimeUnitFilters($options['filter']['delay']);
199			}
200
201			$this->dbFilter('httptest ht', $options, $sqlParts);
202		}
203
204		// limit
205		if (zbx_ctype_digit($options['limit']) && $options['limit']) {
206			$sqlParts['limit'] = $options['limit'];
207		}
208
209		$sqlParts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts);
210		$sqlParts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts);
211		$res = DBselect(self::createSelectQueryFromParts($sqlParts), $sqlParts['limit']);
212		while ($httpTest = DBfetch($res)) {
213			if ($options['countOutput']) {
214				if ($options['groupCount']) {
215					$result[] = $httpTest;
216				}
217				else {
218					$result = $httpTest['rowscount'];
219				}
220			}
221			else {
222				$result[$httpTest['httptestid']] = $httpTest;
223			}
224		}
225
226		if ($options['countOutput']) {
227			return $result;
228		}
229
230		if ($result) {
231			$result = $this->addRelatedObjects($options, $result);
232
233			// expandName
234			$nameRequested = (is_array($options['output']) && in_array('name', $options['output']))
235				|| $options['output'] == API_OUTPUT_EXTEND;
236			$expandName = $options['expandName'] !== null && $nameRequested;
237
238			// expandStepName
239			$stepNameRequested = $options['selectSteps'] == API_OUTPUT_EXTEND
240				|| (is_array($options['selectSteps']) && in_array('name', $options['selectSteps']));
241			$expandStepName = $options['expandStepName'] !== null && $stepNameRequested;
242
243			if ($expandName || $expandStepName) {
244				$result = resolveHttpTestMacros($result, $expandName, $expandStepName);
245			}
246
247			$result = $this->unsetExtraFields($result, ['hostid'], $options['output']);
248		}
249
250		// removing keys (hash -> array)
251		if (!$options['preservekeys']) {
252			$result = zbx_cleanHashes($result);
253		}
254
255		return $result;
256	}
257
258	/**
259	 * Create web scenario.
260	 *
261	 * @param $httptests
262	 *
263	 * @return array
264	 *
265	 * @throws APIException
266	 */
267	public function create($httptests) {
268		$this->validateCreate($httptests);
269
270		$httptests = Manager::HttpTest()->persist($httptests);
271
272		$this->addAuditBulk(AUDIT_ACTION_ADD, AUDIT_RESOURCE_SCENARIO, $httptests);
273
274		return ['httptestids' => zbx_objectValues($httptests, 'httptestid')];
275	}
276
277	/**
278	 * @param array $httptests
279	 *
280	 * @throws APIException if the input is invalid.
281	 */
282	protected function validateCreate(array &$httptests): void {
283		$api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['uuid'], ['hostid', 'name']], 'fields' => [
284			'hostid' =>				['type' => API_ID, 'flags' => API_REQUIRED],
285			'uuid' =>				['type' => API_UUID],
286			'name' =>				['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('httptest', 'name')],
287			'delay' =>				['type' => API_TIME_UNIT, 'flags' => API_NOT_EMPTY | API_ALLOW_USER_MACRO, 'in' => '1:'.SEC_PER_DAY],
288			'retries' =>			['type' => API_INT32, 'in' => '1:10'],
289			'agent' =>				['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest', 'agent')],
290			'http_proxy' =>			['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest', 'http_proxy')],
291			'variables' =>			['type' => API_OBJECTS, 'uniq' => [['name']], 'fields' => [
292				'name' =>				['type' => API_VARIABLE_NAME, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('httptest_field', 'name')],
293				'value' =>				['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('httptest_field', 'value')]
294			]],
295			'headers' =>			['type' => API_OBJECTS, 'fields' => [
296				'name' =>				['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('httptest_field', 'name')],
297				'value' =>				['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('httptest_field', 'value')]
298			]],
299			'status' =>				['type' => API_INT32, 'in' => implode(',', [HTTPTEST_STATUS_ACTIVE, HTTPTEST_STATUS_DISABLED])],
300			'authentication' =>		['type' => API_INT32, 'in' => implode(',', [HTTPTEST_AUTH_NONE, HTTPTEST_AUTH_BASIC, HTTPTEST_AUTH_NTLM, HTTPTEST_AUTH_KERBEROS, HTTPTEST_AUTH_DIGEST])],
301			'http_user' =>			['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest', 'http_user')],
302			'http_password' =>		['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest', 'http_password')],
303			'verify_peer' =>		['type' => API_INT32, 'in' => implode(',', [HTTPTEST_VERIFY_PEER_OFF, HTTPTEST_VERIFY_PEER_ON])],
304			'verify_host' =>		['type' => API_INT32, 'in' => implode(',', [HTTPTEST_VERIFY_HOST_OFF, HTTPTEST_VERIFY_HOST_ON])],
305			'ssl_cert_file' =>		['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest', 'ssl_cert_file')],
306			'ssl_key_file' =>		['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest', 'ssl_key_file')],
307			'ssl_key_password' =>	['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest', 'ssl_key_password')],
308			'steps' =>				['type' => API_OBJECTS, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'uniq' => [['name'], ['no']], 'fields' => [
309				'name' =>				['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('httpstep', 'name')],
310				'no' =>					['type' => API_INT32, 'flags' => API_REQUIRED],
311				'url' =>				['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('httpstep', 'url')],
312				'query_fields' =>		['type' => API_OBJECTS, 'fields' => [
313					'name' =>				['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('httpstep_field', 'name')],
314					'value' =>				['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('httpstep_field', 'value')]
315				]],
316				'posts' =>				['type' => API_HTTP_POST, 'length' => DB::getFieldLength('httpstep', 'posts'), 'name-length' => DB::getFieldLength('httpstep_field', 'name'), 'value-length' => DB::getFieldLength('httpstep_field', 'value')],
317				'variables' =>			['type' => API_OBJECTS, 'uniq' => [['name']], 'fields' => [
318					'name' =>				['type' => API_VARIABLE_NAME, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('httpstep_field', 'name')],
319					'value' =>				['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('httpstep_field', 'value')]
320				]],
321				'headers' =>			['type' => API_OBJECTS, 'fields' => [
322					'name' =>				['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('httpstep_field', 'name')],
323					'value' =>				['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('httpstep_field', 'value')]
324				]],
325				'follow_redirects' =>	['type' => API_INT32, 'in' => implode(',', [HTTPTEST_STEP_FOLLOW_REDIRECTS_OFF, HTTPTEST_STEP_FOLLOW_REDIRECTS_ON])],
326				'retrieve_mode' =>		['type' => API_INT32, 'in' => implode(',', [HTTPTEST_STEP_RETRIEVE_MODE_CONTENT, HTTPTEST_STEP_RETRIEVE_MODE_HEADERS, HTTPTEST_STEP_RETRIEVE_MODE_BOTH])],
327				'timeout' =>			['type' => API_TIME_UNIT, 'flags' => API_NOT_EMPTY | API_ALLOW_USER_MACRO, 'in' => '1:'.SEC_PER_HOUR],
328				'required' =>			['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httpstep', 'required')],
329				'status_codes' =>		['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httpstep', 'status_codes')]
330			]],
331			'tags' =>				['type' => API_OBJECTS, 'uniq' => [['tag', 'value']], 'fields' => [
332				'tag' =>				['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('httptest_tag', 'tag')],
333				'value' =>				['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest_tag', 'value'), 'default' => DB::getDefault('httptest_tag', 'value')]
334			]]
335		]];
336
337		if (!CApiInputValidator::validate($api_input_rules, $httptests, '/', $error)) {
338			self::exception(ZBX_API_ERROR_PARAMETERS, $error);
339		}
340
341		$names_by_hostid = [];
342
343		foreach ($httptests as $httptest) {
344			$names_by_hostid[$httptest['hostid']][] = $httptest['name'];
345		}
346
347		$this->checkAndAddUuid($httptests);
348		$this->checkHostPermissions(array_keys($names_by_hostid));
349		$this->checkDuplicates($names_by_hostid);
350		$this->validateAuthParameters($httptests, __FUNCTION__);
351		$this->validateSslParameters($httptests, __FUNCTION__);
352		$this->validateSteps($httptests, __FUNCTION__);
353	}
354
355	/**
356	 * Check that only httptests on templates have UUID. Add UUID to all httptests on templates, if it does not exists.
357	 *
358	 * @param array $httptests_to_create
359	 *
360	 * @throws APIException
361	 */
362	protected function checkAndAddUuid(array &$httptests_to_create): void {
363		$db_templateids = API::Template()->get([
364			'output' => [],
365			'templateids' => array_column($httptests_to_create, 'hostid'),
366			'preservekeys' => true
367		]);
368
369		foreach ($httptests_to_create as $index => &$httptest) {
370			if (!array_key_exists($httptest['hostid'], $db_templateids) && array_key_exists('uuid', $httptest)) {
371				self::exception(ZBX_API_ERROR_PARAMETERS,
372					_s('Invalid parameter "%1$s": %2$s.', '/' . ($index + 1), _s('unexpected parameter "%1$s"', 'uuid'))
373				);
374			}
375
376			if (array_key_exists($httptest['hostid'], $db_templateids) && !array_key_exists('uuid', $httptest)) {
377				$httptest['uuid'] = generateUuidV4();
378			}
379		}
380		unset($httptest);
381
382		$db_uuid = DB::select('httptest', [
383			'output' => ['uuid'],
384			'filter' => ['uuid' => array_column($httptests_to_create, 'uuid')],
385			'limit' => 1
386		]);
387
388		if ($db_uuid) {
389			self::exception(ZBX_API_ERROR_PARAMETERS,
390				_s('Entry with UUID "%1$s" already exists.', $db_uuid[0]['uuid'])
391			);
392		}
393	}
394
395	/**
396	 * @param $httptests
397	 *
398	 * @return array
399	 */
400	public function update($httptests) {
401		$this->validateUpdate($httptests, $db_httptests);
402
403		Manager::HttpTest()->persist($httptests);
404
405		foreach ($db_httptests as &$db_httptest) {
406			unset($db_httptest['headers'], $db_httptest['variables'], $db_httptest['steps']);
407		}
408		unset($db_httptest);
409
410		$this->addAuditBulk(AUDIT_ACTION_UPDATE, AUDIT_RESOURCE_SCENARIO, $httptests, $db_httptests);
411
412		return ['httptestids' => zbx_objectValues($httptests, 'httptestid')];
413	}
414
415	/**
416	 * @param array $httptests
417	 * @param array $db_httptests
418	 *
419	 * @throws APIException if the input is invalid.
420	 */
421	protected function validateUpdate(array &$httptests, array &$db_httptests = null) {
422		$api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['httptestid']], 'fields' => [
423			'httptestid' =>			['type' => API_ID, 'flags' => API_REQUIRED],
424			'name' =>				['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('httptest', 'name')],
425			'delay' =>				['type' => API_TIME_UNIT, 'flags' => API_NOT_EMPTY | API_ALLOW_USER_MACRO, 'in' => '1:'.SEC_PER_DAY],
426			'retries' =>			['type' => API_INT32, 'in' => '1:10'],
427			'agent' =>				['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest', 'agent')],
428			'http_proxy' =>			['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest', 'http_proxy')],
429			'variables' =>			['type' => API_OBJECTS, 'uniq' => [['name']], 'fields' => [
430				'name' =>				['type' => API_VARIABLE_NAME, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('httptest_field', 'name')],
431				'value' =>				['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('httptest_field', 'value')]
432			]],
433			'headers' =>			['type' => API_OBJECTS, 'fields' => [
434				'name' =>				['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('httptest_field', 'name')],
435				'value' =>				['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('httptest_field', 'value')]
436			]],
437			'status' =>				['type' => API_INT32, 'in' => implode(',', [HTTPTEST_STATUS_ACTIVE, HTTPTEST_STATUS_DISABLED])],
438			'authentication' =>		['type' => API_INT32, 'in' => implode(',', [HTTPTEST_AUTH_NONE, HTTPTEST_AUTH_BASIC, HTTPTEST_AUTH_NTLM, HTTPTEST_AUTH_KERBEROS, HTTPTEST_AUTH_DIGEST])],
439			'http_user' =>			['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest', 'http_user')],
440			'http_password' =>		['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest', 'http_password')],
441			'verify_peer' =>		['type' => API_INT32, 'in' => implode(',', [HTTPTEST_VERIFY_PEER_OFF, HTTPTEST_VERIFY_PEER_ON])],
442			'verify_host' =>		['type' => API_INT32, 'in' => implode(',', [HTTPTEST_VERIFY_HOST_OFF, HTTPTEST_VERIFY_HOST_ON])],
443			'ssl_cert_file' =>		['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest', 'ssl_cert_file')],
444			'ssl_key_file' =>		['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest', 'ssl_key_file')],
445			'ssl_key_password' =>	['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest', 'ssl_key_password')],
446			'steps' =>				['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY, 'uniq' => [['httpstepid'], ['name'], ['no']], 'fields' => [
447				'httpstepid' =>			['type' => API_ID],
448				'name' =>				['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('httpstep', 'name')],
449				'no' =>					['type' => API_INT32],
450				'url' =>				['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('httpstep', 'url')],
451				'query_fields' =>		['type' => API_OBJECTS, 'fields' => [
452					'name' =>				['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('httpstep_field', 'name')],
453					'value' =>				['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('httpstep_field', 'value')]
454				]],
455				'posts' =>				['type' => API_HTTP_POST, 'length' => DB::getFieldLength('httpstep', 'posts'), 'name-length' => DB::getFieldLength('httpstep_field', 'name'), 'value-length' => DB::getFieldLength('httpstep_field', 'value')],
456				'variables' =>			['type' => API_OBJECTS, 'uniq' => [['name']], 'fields' => [
457					'name' =>				['type' => API_VARIABLE_NAME, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('httpstep_field', 'name')],
458					'value' =>				['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('httpstep_field', 'value')]
459				]],
460				'headers' =>			['type' => API_OBJECTS, 'fields' => [
461					'name' =>				['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('httpstep_field', 'name')],
462					'value' =>				['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('httpstep_field', 'value')]
463				]],
464				'follow_redirects' =>	['type' => API_INT32, 'in' => implode(',', [HTTPTEST_STEP_FOLLOW_REDIRECTS_OFF, HTTPTEST_STEP_FOLLOW_REDIRECTS_ON])],
465				'retrieve_mode' =>		['type' => API_INT32, 'in' => implode(',', [HTTPTEST_STEP_RETRIEVE_MODE_CONTENT, HTTPTEST_STEP_RETRIEVE_MODE_HEADERS, HTTPTEST_STEP_RETRIEVE_MODE_BOTH])],
466				'timeout' =>			['type' => API_TIME_UNIT, 'flags' => API_NOT_EMPTY | API_ALLOW_USER_MACRO, 'in' => '1:'.SEC_PER_HOUR],
467				'required' =>			['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httpstep', 'required')],
468				'status_codes' =>		['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httpstep', 'status_codes')]
469			]],
470			'tags' =>				['type' => API_OBJECTS, 'uniq' => [['tag', 'value']], 'fields' => [
471				'tag' =>				['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('httptest_tag', 'tag')],
472				'value' =>				['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('httptest_tag', 'value'), 'default' => DB::getDefault('httptest_tag', 'value')]
473			]]
474		]];
475		if (!CApiInputValidator::validate($api_input_rules, $httptests, '/', $error)) {
476			self::exception(ZBX_API_ERROR_PARAMETERS, $error);
477		}
478
479		// permissions
480		$db_httptests = $this->get([
481			'output' => ['httptestid', 'hostid', 'name', 'delay', 'retries', 'agent', 'http_proxy',
482				'status', 'authentication', 'http_user', 'http_password', 'verify_peer', 'verify_host',
483				'ssl_cert_file', 'ssl_key_file', 'ssl_key_password', 'templateid'
484			],
485			'selectSteps' => ['httpstepid', 'name', 'no', 'url', 'timeout', 'posts', 'required',
486				'status_codes', 'follow_redirects', 'retrieve_mode', 'post_type'
487			],
488			'httptestids' => zbx_objectValues($httptests, 'httptestid'),
489			'editable' => true,
490			'preservekeys' => true
491		]);
492
493		foreach ($db_httptests as &$db_httptest) {
494			$db_httptest['headers'] = [];
495			$db_httptest['variables'] = [];
496			$db_httptest['steps'] = zbx_toHash($db_httptest['steps'], 'httpstepid');
497		}
498		unset($db_httptest);
499
500		$names_by_hostid = [];
501
502		foreach ($httptests as $httptest) {
503			if (!array_key_exists($httptest['httptestid'], $db_httptests)) {
504				self::exception(ZBX_API_ERROR_PERMISSIONS,
505					_('No permissions to referred object or it does not exist!')
506				);
507			}
508
509			$db_httptest = $db_httptests[$httptest['httptestid']];
510
511			if (array_key_exists('name', $httptest)) {
512				if ($db_httptest['templateid'] != 0) {
513					self::exception(ZBX_API_ERROR_PARAMETERS, _s(
514						'Cannot update a templated web scenario "%1$s": %2$s.', $httptest['name'],
515						_s('unexpected parameter "%1$s"', 'name')
516					));
517				}
518
519				if ($httptest['name'] !== $db_httptest['name']) {
520					$names_by_hostid[$db_httptest['hostid']][] = $httptest['name'];
521				}
522			}
523		}
524
525		$httptests = $this->extendObjectsByKey($httptests, $db_httptests, 'httptestid', ['hostid', 'name']);
526
527		// uniqueness
528		foreach ($httptests as &$httptest) {
529			$db_httptest = $db_httptests[$httptest['httptestid']];
530
531			if (array_key_exists('steps', $httptest)) {
532				// unexpected patameters for templated web scenario steps
533				if ($db_httptest['templateid'] != 0) {
534					foreach ($httptest['steps'] as $httpstep) {
535						foreach (['name', 'no'] as $field_name) {
536							if (array_key_exists($field_name, $httpstep)) {
537								self::exception(ZBX_API_ERROR_PARAMETERS, _s(
538									'Cannot update step for a templated web scenario "%1$s": %2$s.', $httptest['name'],
539									_s('unexpected parameter "%1$s"', $field_name)
540								));
541							}
542						}
543					}
544				}
545
546				$httptest['steps'] =
547					$this->extendObjectsByKey($httptest['steps'], $db_httptest['steps'], 'httpstepid', ['name']);
548			}
549		}
550		unset($httptest);
551
552		$api_input_rules = ['type' => API_OBJECTS, 'uniq' => [['hostid', 'name']], 'fields' => [
553			'hostid' =>	['type' => API_ID],
554			'name' =>	['type' => API_STRING_UTF8],
555			'steps' =>	['type' => API_OBJECTS, 'uniq' => [['name']], 'fields' => [
556				'name' =>	['type' => API_STRING_UTF8]
557			]]
558		]];
559
560		if (!CApiInputValidator::validateUniqueness($api_input_rules, $httptests, '/', $error)) {
561			self::exception(ZBX_API_ERROR_PARAMETERS, $error);
562		}
563
564		// validation
565		if ($names_by_hostid) {
566			$this->checkDuplicates($names_by_hostid);
567		}
568		$this->validateAuthParameters($httptests, __FUNCTION__, $db_httptests);
569		$this->validateSslParameters($httptests, __FUNCTION__, $db_httptests);
570		$this->validateSteps($httptests, __FUNCTION__, $db_httptests);
571
572		return $httptests;
573	}
574
575	/**
576	 * Delete web scenario.
577	 *
578	 * @param array $httptestids
579	 * @param bool  $nopermissions
580	 *
581	 * @return array
582	 */
583	public function delete(array $httptestids, $nopermissions = false) {
584		// TODO: remove $nopermissions hack
585
586		$api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true];
587		if (!CApiInputValidator::validate($api_input_rules, $httptestids, '/', $error)) {
588			self::exception(ZBX_API_ERROR_PARAMETERS, $error);
589		}
590
591		$db_httptests = $this->get([
592			'output' => ['httptestid', 'name', 'templateid'],
593			'httptestids' => $httptestids,
594			'editable' => true,
595			'preservekeys' => true
596		]);
597
598		if (!$nopermissions) {
599			foreach ($httptestids as $httptestid) {
600				if (!array_key_exists($httptestid, $db_httptests)) {
601					self::exception(ZBX_API_ERROR_PERMISSIONS,
602						_('No permissions to referred object or it does not exist!')
603					);
604				}
605
606				$db_httptest = $db_httptests[$httptestid];
607
608				if ($db_httptest['templateid'] != 0) {
609					self::exception(ZBX_API_ERROR_PARAMETERS,
610						_s('Cannot delete templated web scenario "%1$s".', $db_httptest['name'])
611					);
612				}
613			}
614		}
615
616		$parent_httptestids = $httptestids;
617		$child_httptestids = [];
618		do {
619			$parent_httptestids = array_keys(DB::select('httptest', [
620				'output' => [],
621				'filter' => ['templateid' => $parent_httptestids],
622				'preservekeys' => true
623			]));
624
625			$child_httptestids = array_merge($child_httptestids, $parent_httptestids);
626		}
627		while ($parent_httptestids);
628
629		$del_httptestids = array_merge($httptestids, $child_httptestids);
630		$del_itemids = [];
631
632		$db_httptestitems = DBselect(
633			'SELECT hti.itemid'.
634			' FROM httptestitem hti'.
635			' WHERE '.dbConditionInt('hti.httptestid', $del_httptestids)
636		);
637		while ($db_httptestitem = DBfetch($db_httptestitems)) {
638			$del_itemids[] = $db_httptestitem['itemid'];
639		}
640
641		$db_httpstepitems = DBselect(
642			'SELECT hsi.itemid'.
643			' FROM httpstepitem hsi,httpstep hs'.
644			' WHERE hsi.httpstepid=hs.httpstepid'.
645				' AND '.dbConditionInt('hs.httptestid', $del_httptestids)
646		);
647		while ($db_httpstepitem = DBfetch($db_httpstepitems)) {
648			$del_itemids[] = $db_httpstepitem['itemid'];
649		}
650
651		if ($del_itemids) {
652			CItemManager::delete($del_itemids);
653		}
654
655		DB::delete('httptest', ['httptestid' => $del_httptestids]);
656
657		$this->addAuditBulk(AUDIT_ACTION_DELETE, AUDIT_RESOURCE_SCENARIO, $db_httptests);
658
659		return ['httptestids' => $httptestids];
660	}
661
662	/**
663	 * Checks if the current user has access to the given hosts and templates.
664	 *
665	 * @param array $hostids  an array of host or template IDs
666	 *
667	 * @throws APIException if the user doesn't have write permissions for the given hosts.
668	 */
669	private function checkHostPermissions(array $hostids) {
670		if ($hostids) {
671			$count = API::Host()->get([
672				'countOutput' => true,
673				'hostids' => $hostids,
674				'editable' => true
675			]);
676
677			if ($count == count($hostids)) {
678				return;
679			}
680
681			$count += API::Template()->get([
682				'countOutput' => true,
683				'templateids' => $hostids,
684				'editable' => true
685			]);
686
687			if ($count != count($hostids)) {
688				self::exception(ZBX_API_ERROR_PERMISSIONS,
689					_('No permissions to referred object or it does not exist!')
690				);
691			}
692		}
693	}
694
695	/**
696	 * Check for duplicated web scenarios.
697	 *
698	 * @param array $names_by_hostid
699	 *
700	 * @throws APIException  if web scenario already exists.
701	 */
702	private function checkDuplicates(array $names_by_hostid) {
703		$sql_where = [];
704		foreach ($names_by_hostid as $hostid => $names) {
705			$sql_where[] = '(ht.hostid='.$hostid.' AND '.dbConditionString('ht.name', $names).')';
706		}
707
708		$db_httptests = DBfetchArray(
709			DBselect('SELECT ht.name FROM httptest ht WHERE '.implode(' OR ', $sql_where), 1)
710		);
711
712		if ($db_httptests) {
713			self::exception(ZBX_API_ERROR_PARAMETERS,
714				_s('Web scenario "%1$s" already exists.', $db_httptests[0]['name'])
715			);
716		}
717	}
718
719	/**
720	 * @param array  $httptests
721	 * @param string $method
722	 * @param array  $db_httptests
723	 *
724	 * @throws APIException
725	 */
726	protected function validateSteps(array &$httptests, $method, array $db_httptests = null) {
727		if ($method === 'validateUpdate') {
728			foreach ($httptests as $httptest) {
729				if (!array_key_exists('steps', $httptest)) {
730					continue;
731				}
732
733				$db_httptest = $db_httptests[$httptest['httptestid']];
734
735				if ($db_httptest['templateid'] != 0) {
736					if (count($httptest['steps']) != count($db_httptest['steps'])) {
737						self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect templated web scenario step count.'));
738					}
739
740					foreach ($httptest['steps'] as $httpstep) {
741						if (!array_key_exists('httpstepid', $httpstep)) {
742							self::exception(ZBX_API_ERROR_PARAMETERS, _s(
743								'Cannot update step for a templated web scenario "%1$s": %2$s.', $httptest['name'],
744								_s('the parameter "%1$s" is missing', 'httpstepid')
745							));
746						}
747						elseif (!array_key_exists($httpstep['httpstepid'], $db_httptest['steps'])) {
748							self::exception(ZBX_API_ERROR_PARAMETERS,
749								_('No permissions to referred object or it does not exist!')
750							);
751						}
752					}
753				}
754			}
755		}
756
757		$this->checkStatusCodes($httptests);
758		$this->validateRetrieveMode($httptests, $method, $db_httptests);
759	}
760
761	/**
762	 * Validate http response code range.
763	 * Range can be empty string or list of comma separated numeric strings or user macros.
764	 *
765	 * Examples: '100-199, 301, 404, 500-550, {$MACRO}-200, {$MACRO}-{$MACRO}'
766	 *
767	 * @param array $httptests
768	 *
769	 * @throws APIException if the status code range is invalid.
770	 */
771	private function checkStatusCodes(array $httptests) {
772		$ranges_parser = new CRangesParser(['usermacros' => true]);
773
774		foreach ($httptests as $httptest) {
775			if (!array_key_exists('steps', $httptest)) {
776				continue;
777			}
778
779			foreach ($httptest['steps'] as $httpstep) {
780				if (!array_key_exists('status_codes', $httpstep) || $httpstep['status_codes'] === '') {
781					continue;
782				}
783
784				if ($ranges_parser->parse($httpstep['status_codes']) != CParser::PARSE_SUCCESS) {
785					self::exception(ZBX_API_ERROR_PARAMETERS,
786						_s('Invalid response code "%1$s".', $httpstep['status_codes'])
787					);
788				}
789			}
790		}
791	}
792
793	protected function applyQueryOutputOptions($tableName, $tableAlias, array $options, array $sqlParts) {
794		$sqlParts = parent::applyQueryOutputOptions($tableName, $tableAlias, $options, $sqlParts);
795
796		if (!$options['countOutput']) {
797			// make sure we request the hostid to be able to expand macros
798			if ($options['expandName'] !== null || $options['expandStepName'] !== null || $options['selectHosts'] !== null) {
799				$sqlParts = $this->addQuerySelect($this->fieldId('hostid'), $sqlParts);
800			}
801		}
802
803		return $sqlParts;
804	}
805
806	protected function addRelatedObjects(array $options, array $result) {
807		$result = parent::addRelatedObjects($options, $result);
808
809		$httpTestIds = array_keys($result);
810
811		// adding headers and variables
812		$fields = [
813			ZBX_HTTPFIELD_HEADER => 'headers',
814			ZBX_HTTPFIELD_VARIABLE => 'variables'
815		];
816		foreach ($fields as $type => $field) {
817			if (!$this->outputIsRequested($field, $options['output'])) {
818				unset($fields[$type]);
819			}
820		}
821
822		if ($fields) {
823			$db_httpfields = DB::select('httptest_field', [
824				'output' => ['httptestid', 'name', 'value', 'type'],
825				'filter' => [
826					'httptestid' => $httpTestIds,
827					'type' => array_keys($fields)
828				],
829				'sortfield' => ['httptest_fieldid']
830			]);
831
832			foreach ($result as &$httptest) {
833				foreach ($fields as $field) {
834					$httptest[$field] = [];
835				}
836			}
837			unset($httptest);
838
839			foreach ($db_httpfields as $db_httpfield) {
840				$result[$db_httpfield['httptestid']][$fields[$db_httpfield['type']]][] = [
841					'name' => $db_httpfield['name'],
842					'value' => $db_httpfield['value']
843				];
844			}
845		}
846
847		// adding hosts
848		if ($options['selectHosts'] !== null && $options['selectHosts'] != API_OUTPUT_COUNT) {
849			$relationMap = $this->createRelationMap($result, 'httptestid', 'hostid');
850			$hosts = API::Host()->get([
851				'output' => $options['selectHosts'],
852				'hostid' => $relationMap->getRelatedIds(),
853				'nopermissions' => true,
854				'templated_hosts' => true,
855				'preservekeys' => true
856			]);
857			$result = $relationMap->mapMany($result, $hosts, 'hosts');
858		}
859
860		// adding steps
861		if ($options['selectSteps'] !== null) {
862			if ($options['selectSteps'] != API_OUTPUT_COUNT) {
863				$fields = [
864					ZBX_HTTPFIELD_HEADER => 'headers',
865					ZBX_HTTPFIELD_VARIABLE => 'variables',
866					ZBX_HTTPFIELD_QUERY_FIELD => 'query_fields',
867					ZBX_HTTPFIELD_POST_FIELD => 'posts'
868				];
869				foreach ($fields as $type => $field) {
870					if (!$this->outputIsRequested($field, $options['selectSteps'])) {
871						unset($fields[$type]);
872					}
873				}
874
875				$db_httpsteps = API::getApiService()->select('httpstep', [
876					'output' => $this->outputExtend($options['selectSteps'], ['httptestid', 'httpstepid', 'post_type']),
877					'filter' => ['httptestid' => $httpTestIds],
878					'preservekeys' => true
879				]);
880				$relationMap = $this->createRelationMap($db_httpsteps, 'httptestid', 'httpstepid');
881
882				if ($fields) {
883					foreach ($db_httpsteps as &$db_httpstep) {
884						foreach ($fields as $type => $field) {
885							if ($type != ZBX_HTTPFIELD_POST_FIELD || $db_httpstep['post_type'] == ZBX_POSTTYPE_FORM) {
886								$db_httpstep[$field] = [];
887							}
888						}
889					}
890					unset($db_httpstep);
891
892					$db_httpstep_fields = DB::select('httpstep_field', [
893						'output' => ['httpstepid', 'name', 'value', 'type'],
894						'filter' => [
895							'httpstepid' => array_keys($db_httpsteps),
896							'type' => array_keys($fields)
897						],
898						'sortfield' => ['httpstep_fieldid']
899					]);
900
901					foreach ($db_httpstep_fields as $db_httpstep_field) {
902						$db_httpstep = &$db_httpsteps[$db_httpstep_field['httpstepid']];
903
904						if ($db_httpstep_field['type'] != ZBX_HTTPFIELD_POST_FIELD
905								|| $db_httpstep['post_type'] == ZBX_POSTTYPE_FORM) {
906							$db_httpstep[$fields[$db_httpstep_field['type']]][] = [
907								'name' => $db_httpstep_field['name'],
908								'value' => $db_httpstep_field['value']
909							];
910						}
911					}
912					unset($db_httpstep);
913				}
914
915				$db_httpsteps = $this->unsetExtraFields($db_httpsteps, ['httptestid', 'httpstepid', 'post_type'],
916					$options['selectSteps']
917				);
918				$result = $relationMap->mapMany($result, $db_httpsteps, 'steps');
919			}
920			else {
921				$dbHttpSteps = DBselect(
922					'SELECT hs.httptestid,COUNT(hs.httpstepid) AS stepscnt'.
923						' FROM httpstep hs'.
924						' WHERE '.dbConditionInt('hs.httptestid', $httpTestIds).
925						' GROUP BY hs.httptestid'
926				);
927				while ($dbHttpStep = DBfetch($dbHttpSteps)) {
928					$result[$dbHttpStep['httptestid']]['steps'] = $dbHttpStep['stepscnt'];
929				}
930			}
931		}
932
933		// Adding web scenario tags.
934		if ($options['selectTags'] !== null) {
935			$options['selectTags'] = ($options['selectTags'] !== API_OUTPUT_EXTEND)
936				? (array) $options['selectTags']
937				: ['tag', 'value'];
938
939			$options['selectTags'] = array_intersect(['tag', 'value'], $options['selectTags']);
940			$requested_output = array_flip($options['selectTags']);
941
942			$db_tags = DBselect(
943				'SELECT '.implode(',', array_merge($options['selectTags'], ['httptestid'])).
944				' FROM httptest_tag'.
945				' WHERE '.dbConditionInt('httptestid', $httpTestIds)
946			);
947
948			array_walk($result, function (&$http_test) {
949				$http_test['tags'] = [];
950			});
951
952			while ($db_tag = DBfetch($db_tags)) {
953				$result[$db_tag['httptestid']]['tags'][] = array_intersect_key($db_tag, $requested_output);
954			}
955		}
956
957		return $result;
958	}
959
960	/**
961	 * @param array  $httptests
962	 * @param string $method
963	 * @param array  $db_httptests
964	 *
965	 * @throws APIException  if auth parameters are invalid.
966	 */
967	private function validateAuthParameters(array &$httptests, $method, array $db_httptests = null) {
968		foreach ($httptests as &$httptest) {
969			if (array_key_exists('authentication', $httptest) || array_key_exists('http_user', $httptest)
970					|| array_key_exists('http_password', $httptest)) {
971				$httptest += [
972					'authentication' => ($method === 'validateUpdate')
973						? $db_httptests[$httptest['httptestid']]['authentication']
974						: HTTPTEST_AUTH_NONE
975				];
976
977				if ($httptest['authentication'] == HTTPTEST_AUTH_NONE) {
978					foreach (['http_user', 'http_password'] as $field_name) {
979						$httptest += [$field_name => ''];
980
981						if ($httptest[$field_name] !== '') {
982							self::exception(ZBX_API_ERROR_PARAMETERS,
983								_s('Incorrect value for field "%1$s": %2$s.', $field_name, _('should be empty'))
984							);
985						}
986					}
987				}
988			}
989		}
990		unset($httptest);
991	}
992
993	/**
994	 * @param array  $httptests
995	 * @param string $method
996	 * @param array  $db_httptests
997	 *
998	 * @throws APIException if SSL cert is present but SSL key is not.
999	 */
1000	private function validateSslParameters(array &$httptests, $method, array $db_httptests = null) {
1001		foreach ($httptests as &$httptest) {
1002			if (array_key_exists('ssl_key_password', $httptest)
1003					|| array_key_exists('ssl_key_file', $httptest)
1004					|| array_key_exists('ssl_cert_file', $httptest)) {
1005				if ($method === 'validateCreate') {
1006					$httptest += [
1007						'ssl_key_password' => '',
1008						'ssl_key_file' => '',
1009						'ssl_cert_file' => ''
1010					];
1011				}
1012				else {
1013					$db_httptest = $db_httptests[$httptest['httptestid']];
1014					$httptest += [
1015						'ssl_key_password' => $db_httptest['ssl_key_password'],
1016						'ssl_key_file' => $db_httptest['ssl_key_file'],
1017						'ssl_cert_file' => $db_httptest['ssl_cert_file']
1018					];
1019				}
1020
1021				if ($httptest['ssl_key_password'] != '' && $httptest['ssl_key_file'] == '') {
1022					self::exception(ZBX_API_ERROR_PARAMETERS,
1023						_s('Empty SSL key file for web scenario "%1$s".', $httptest['name'])
1024					);
1025				}
1026
1027				if ($httptest['ssl_key_file'] != '' && $httptest['ssl_cert_file'] == '') {
1028					self::exception(ZBX_API_ERROR_PARAMETERS,
1029						_s('Empty SSL certificate file for web scenario "%1$s".', $httptest['name'])
1030					);
1031				}
1032			}
1033		}
1034		unset($httptest);
1035	}
1036
1037	/**
1038	 * @param array  $httptests
1039	 * @param string $method
1040	 * @param array  $db_httptests
1041	 *
1042	 * @throws APIException if parameters is invalid.
1043	 */
1044	private function validateRetrieveMode(array &$httptests, $method, array $db_httptests = null) {
1045		foreach ($httptests as &$httptest) {
1046			if (!array_key_exists('steps', $httptest)) {
1047				continue;
1048			}
1049
1050			foreach ($httptest['steps'] as &$httpstep) {
1051				if (array_key_exists('retrieve_mode', $httpstep)
1052						|| array_key_exists('posts', $httpstep)
1053						|| array_key_exists('required', $httpstep)) {
1054
1055					if ($method === 'validateCreate' || !array_key_exists('httpstepid', $httpstep)) {
1056						$httpstep += [
1057							'retrieve_mode' => HTTPTEST_STEP_RETRIEVE_MODE_CONTENT,
1058							'posts' => '',
1059							'required' => ''
1060						];
1061					}
1062					else {
1063						$db_httptest = $db_httptests[$httptest['httptestid']];
1064						$db_httpstep = $db_httptest['steps'][$httpstep['httpstepid']];
1065						$httpstep += [
1066							'retrieve_mode' => $db_httpstep['retrieve_mode'],
1067							'required' => $db_httpstep['required'],
1068							'posts' => ($db_httpstep['retrieve_mode'] != HTTPTEST_STEP_RETRIEVE_MODE_HEADERS)
1069								? $db_httpstep['posts']
1070								: ''
1071						];
1072					}
1073
1074					if ($httpstep['retrieve_mode'] == HTTPTEST_STEP_RETRIEVE_MODE_HEADERS) {
1075						if ($httpstep['posts'] !== '' && $httpstep['posts'] !== []) {
1076							$field_name = $httpstep['required'] !== '' ? 'required' : 'posts';
1077
1078							self::exception(ZBX_API_ERROR_PARAMETERS,
1079								_s('Incorrect value for field "%1$s": %2$s.', 'posts', _('should be empty'))
1080							);
1081						}
1082					}
1083				}
1084			}
1085			unset($httpstep);
1086		}
1087		unset($httptest);
1088	}
1089}
1090