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