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