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