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
22class CJsonRpc {
23
24	const VERSION = '2.0';
25
26	public $json;
27
28	/**
29	 * API client to use for making requests.
30	 *
31	 * @var CApiClient
32	 */
33	protected $apiClient;
34
35	private $_response;
36	private $_error_list;
37	private $_zbx2jsonErrors;
38	private $_jsonDecoded;
39
40	/**
41	 * Constructor.
42	 *
43	 * @param CApiClient $apiClient
44	 * @param string $data
45	 */
46	public function __construct(CApiClient $apiClient, $data) {
47		$this->apiClient = $apiClient;
48
49		$this->json = new CJson();
50		$this->initErrors();
51
52		$this->_response = [];
53		$this->_jsonDecoded = $this->json->decode($data, true);
54	}
55
56	/**
57	 * Executes API requests.
58	 *
59	 * @return string JSON encoded value
60	 */
61	public function execute() {
62		if ($this->json->hasError()) {
63			$this->jsonError([], '-32700', null, null, true);
64			return $this->json->encode($this->_response[0], [], false, false);
65		}
66
67		if (!is_array($this->_jsonDecoded) || $this->_jsonDecoded == []) {
68			$this->jsonError([], '-32600', null, null, true);
69			return $this->json->encode($this->_response[0], [], false, false);
70		}
71
72		foreach (zbx_toArray($this->_jsonDecoded) as $call) {
73			if (!$this->validate($call)) {
74				continue;
75			}
76
77			list($api, $method) = explode('.', $call['method']) + [1 => ''];
78			$result = $this->apiClient->callMethod($api, $method, $call['params'], $call['auth']);
79
80			$this->processResult($call, $result);
81		}
82
83		if ($this->_response === array_fill(0, count($this->_response), null)) {
84			return '';
85		}
86
87		if (is_array($this->_jsonDecoded)
88				&& array_keys($this->_jsonDecoded) === range(0, count($this->_jsonDecoded) - 1)) {
89			// Return response as encoded batch if $this->_jsonDecoded is associative array.
90			return $this->json->encode(array_values(array_filter($this->_response)), [], false, false);
91		}
92
93		return ($this->_response[0] !== null) ? $this->json->encode($this->_response[0], [], false, false) : '';
94	}
95
96	public function validate(&$call) {
97		if (is_array($call)) {
98			$call = array_intersect_key($call, array_flip(['jsonrpc', 'method', 'params', 'auth', 'id']));
99		}
100
101		$api_input_rules = ['type' => API_OBJECT, 'fields' => [
102			'jsonrpc' =>	['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'in' => self::VERSION],
103			'method' =>		['type' => API_STRING_UTF8, 'flags' => API_REQUIRED],
104			'params' =>		['type' => API_JSONRPC_PARAMS, 'flags' => API_REQUIRED],
105			'auth' =>		['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY | API_ALLOW_NULL, 'default' => null],
106			'id' =>			['type' => API_JSONRPC_ID]
107		]];
108
109		if (!CApiInputValidator::validate($api_input_rules, $call, '/', $error)) {
110			$call_id = is_array($call) ? array_intersect_key($call, array_flip(['id'])) : [];
111			$api_input_rules = ['type' => API_OBJECT, 'fields' => [
112				'id' =>	['type' => API_JSONRPC_ID]
113			]];
114
115			if (!CApiInputValidator::validate($api_input_rules, $call_id, '', $err)) {
116				$call_id = [];
117			}
118
119			$this->jsonError($call_id, '-32600', $error, null, true);
120
121			return false;
122		}
123
124		return true;
125	}
126
127	public function processResult(array $call, CApiClientResponse $response) {
128		if ($response->errorCode) {
129			$errno = $this->_zbx2jsonErrors[$response->errorCode];
130
131			$this->jsonError($call, $errno, $response->errorMessage, $response->debug);
132		}
133		else {
134			// Notifications (request object without an "id" member) MUST NOT be answered.
135			$this->_response[] = array_key_exists('id', $call)
136				? [
137					'jsonrpc' => self::VERSION,
138					'result' => $response->data,
139					'id' => $call['id']
140				]
141				: null;
142		}
143	}
144
145	private function jsonError(array $call, $errno, $data = null, $debug = null, $force_err = false) {
146		// Notifications MUST NOT be answered, but error MUST be generated on JSON parse error
147		if (!$force_err && !array_key_exists('id', $call)) {
148			$this->_response[] = null;
149			return;
150		}
151
152		if (!array_key_exists($errno, $this->_error_list)) {
153			$data = _s('JSON-RPC error generation failed. No such error "%1$s".', $errno);
154			$errno = '-32400';
155		}
156
157		$error = $this->_error_list[$errno];
158
159		if ($data !== null) {
160			$error['data'] = $data;
161		}
162
163		if ($debug !== null) {
164			$error['debug'] = $debug;
165		}
166
167		$this->_response[] = [
168			'jsonrpc' => self::VERSION,
169			'error' => $error,
170			'id' => array_key_exists('id', $call) ? $call['id'] : null
171		];
172	}
173
174	private function initErrors() {
175		$this->_error_list = [
176			'-32700' => [
177				'code' => -32700,
178				'message' => _('Parse error'),
179				'data' => _('Invalid JSON. An error occurred on the server while parsing the JSON text.')
180			],
181			'-32600' => [
182				'code' => -32600,
183				'message' => _('Invalid Request.'),
184				'data' => _('The received JSON is not a valid JSON-RPC Request.')
185			],
186			'-32601' => [
187				'code' => -32601,
188				'message' => _('Method not found.'),
189				'data' => _('The requested remote-procedure does not exist / is not available')
190			],
191			'-32602' => [
192				'code' => -32602,
193				'message' => _('Invalid params.'),
194				'data' => _('Invalid method parameters.')
195			],
196			'-32603' => [
197				'code' => -32603,
198				'message' => _('Internal error.'),
199				'data' => _('Internal JSON-RPC error.')
200			],
201			'-32500' => [
202				'code' => -32500,
203				'message' => _('Application error.'),
204				'data' => _('No details')
205			],
206			'-32400' => [
207				'code' => -32400,
208				'message' => _('System error.'),
209				'data' => _('No details')
210			],
211			'-32300' => [
212				'code' => -32300,
213				'message' => _('Transport error.'),
214				'data' => _('No details')
215			]
216		];
217
218		$this->_zbx2jsonErrors = [
219			ZBX_API_ERROR_NO_METHOD => '-32601',
220			ZBX_API_ERROR_PARAMETERS => '-32602',
221			ZBX_API_ERROR_NO_AUTH => '-32602',
222			ZBX_API_ERROR_PERMISSIONS => '-32500',
223			ZBX_API_ERROR_INTERNAL => '-32500'
224		];
225	}
226}
227