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 $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 95 'jsonrpc' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'in' => self::VERSION], 96 'method' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED], 97 'params' => ['type' => API_JSONRPC_PARAMS, 'flags' => API_REQUIRED], 98 'auth' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY | API_ALLOW_NULL, 'default' => null], 99 'id' => ['type' => API_JSONRPC_ID] 100 ]]; 101 102 if (!CApiInputValidator::validate($api_input_rules, $call, '/', $error)) { 103 $call_id = is_array($call) ? array_intersect_key($call, array_flip(['id'])) : []; 104 $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 105 'id' => ['type' => API_JSONRPC_ID] 106 ]]; 107 108 if (!CApiInputValidator::validate($api_input_rules, $call_id, '', $err)) { 109 $call_id = []; 110 } 111 112 $this->jsonError($call_id, '-32600', $error, null, true); 113 114 return false; 115 } 116 117 return true; 118 } 119 120 public function processResult(array $call, CApiClientResponse $response) { 121 if ($response->errorCode) { 122 $errno = $this->_zbx2jsonErrors[$response->errorCode]; 123 124 $this->jsonError($call, $errno, $response->errorMessage, $response->debug); 125 } 126 else { 127 // Notifications (request object without an "id" member) MUST NOT be answered. 128 $this->_response[] = array_key_exists('id', $call) 129 ? [ 130 'jsonrpc' => self::VERSION, 131 'result' => $response->data, 132 'id' => $call['id'] 133 ] 134 : null; 135 } 136 } 137 138 private function jsonError(array $call, $errno, $data = null, $debug = null, $force_err = false) { 139 // Notifications MUST NOT be answered, but error MUST be generated on JSON parse error 140 if (!$force_err && !array_key_exists('id', $call)) { 141 $this->_response[] = null; 142 return; 143 } 144 145 if (!array_key_exists($errno, $this->_error_list)) { 146 $data = _s('JSON-RPC error generation failed. No such error "%1$s".', $errno); 147 $errno = '-32400'; 148 } 149 150 $error = $this->_error_list[$errno]; 151 152 if ($data !== null) { 153 $error['data'] = $data; 154 } 155 156 if ($debug !== null) { 157 $error['debug'] = $debug; 158 } 159 160 $this->_response[] = [ 161 'jsonrpc' => self::VERSION, 162 'error' => $error, 163 'id' => array_key_exists('id', $call) ? $call['id'] : null 164 ]; 165 } 166 167 private function initErrors() { 168 $this->_error_list = [ 169 '-32700' => [ 170 'code' => -32700, 171 'message' => _('Parse error'), 172 'data' => _('Invalid JSON. An error occurred on the server while parsing the JSON text.') 173 ], 174 '-32600' => [ 175 'code' => -32600, 176 'message' => _('Invalid Request.'), 177 'data' => _('The received JSON is not a valid JSON-RPC Request.') 178 ], 179 '-32601' => [ 180 'code' => -32601, 181 'message' => _('Method not found.'), 182 'data' => _('The requested remote-procedure does not exist / is not available') 183 ], 184 '-32602' => [ 185 'code' => -32602, 186 'message' => _('Invalid params.'), 187 'data' => _('Invalid method parameters.') 188 ], 189 '-32603' => [ 190 'code' => -32603, 191 'message' => _('Internal error.'), 192 'data' => _('Internal JSON-RPC error.') 193 ], 194 '-32500' => [ 195 'code' => -32500, 196 'message' => _('Application error.'), 197 'data' => _('No details') 198 ], 199 '-32400' => [ 200 'code' => -32400, 201 'message' => _('System error.'), 202 'data' => _('No details') 203 ], 204 '-32300' => [ 205 'code' => -32300, 206 'message' => _('Transport error.'), 207 'data' => _('No details') 208 ] 209 ]; 210 211 $this->_zbx2jsonErrors = [ 212 ZBX_API_ERROR_NO_METHOD => '-32601', 213 ZBX_API_ERROR_PARAMETERS => '-32602', 214 ZBX_API_ERROR_NO_AUTH => '-32602', 215 ZBX_API_ERROR_PERMISSIONS => '-32500', 216 ZBX_API_ERROR_INTERNAL => '-32500' 217 ]; 218 } 219} 220