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