gggeek /
phpxmlrpc
| 1 | <?php |
||
| 2 | |||
| 3 | namespace PhpXmlRpc; |
||
| 4 | |||
| 5 | use PhpXmlRpc\Exception\ValueErrorException; |
||
| 6 | use PhpXmlRpc\Helper\XMLParser; |
||
| 7 | use PhpXmlRpc\Traits\CharsetEncoderAware; |
||
| 8 | use PhpXmlRpc\Traits\DeprecationLogger; |
||
| 9 | |||
| 10 | /** |
||
| 11 | * Used to represent a client of an XML-RPC server. |
||
| 12 | * |
||
| 13 | * @property int $errno deprecated - public access left in purely for BC. |
||
| 14 | * @property string $errstr deprecated - public access left in purely for BC. |
||
| 15 | * @property string $method deprecated - public access left in purely for BC. Access via getUrl()/__construct() |
||
| 16 | * @property string $server deprecated - public access left in purely for BC. Access via getUrl()/__construct() |
||
| 17 | * @property int $port deprecated - public access left in purely for BC. Access via getUrl()/__construct() |
||
| 18 | * @property string $path deprecated - public access left in purely for BC. Access via getUrl()/__construct() |
||
| 19 | */ |
||
| 20 | class Client |
||
| 21 | { |
||
| 22 | use DeprecationLogger; |
||
| 23 | //use CharsetEncoderAware; |
||
| 24 | |||
| 25 | const USE_CURL_NEVER = 0; |
||
| 26 | const USE_CURL_ALWAYS = 1; |
||
| 27 | const USE_CURL_AUTO = 2; |
||
| 28 | |||
| 29 | const OPT_ACCEPTED_CHARSET_ENCODINGS = 'accepted_charset_encodings'; |
||
| 30 | const OPT_ACCEPTED_COMPRESSION = 'accepted_compression'; |
||
| 31 | const OPT_AUTH_TYPE = 'authtype'; |
||
| 32 | const OPT_CA_CERT = 'cacert'; |
||
| 33 | const OPT_CA_CERT_DIR = 'cacertdir'; |
||
| 34 | const OPT_CERT = 'cert'; |
||
| 35 | const OPT_CERT_PASS = 'certpass'; |
||
| 36 | const OPT_COOKIES = 'cookies'; |
||
| 37 | const OPT_DEBUG = 'debug'; |
||
| 38 | const OPT_EXTRA_CURL_OPTS = 'extracurlopts'; |
||
| 39 | const OPT_EXTRA_SOCKET_OPTS = 'extrasockopts'; |
||
| 40 | const OPT_KEEPALIVE = 'keepalive'; |
||
| 41 | const OPT_KEY = 'key'; |
||
| 42 | const OPT_KEY_PASS = 'keypass'; |
||
| 43 | const OPT_NO_MULTICALL = 'no_multicall'; |
||
| 44 | const OPT_PASSWORD = 'password'; |
||
| 45 | const OPT_PROXY = 'proxy'; |
||
| 46 | const OPT_PROXY_AUTH_TYPE = 'proxy_authtype'; |
||
| 47 | const OPT_PROXY_PASS = 'proxy_pass'; |
||
| 48 | const OPT_PROXY_PORT = 'proxyport'; |
||
| 49 | const OPT_PROXY_USER = 'proxy_user'; |
||
| 50 | const OPT_REQUEST_CHARSET_ENCODING = 'request_charset_encoding'; |
||
| 51 | const OPT_REQUEST_COMPRESSION = 'request_compression'; |
||
| 52 | const OPT_RETURN_TYPE = 'return_type'; |
||
| 53 | const OPT_SSL_VERSION = 'sslversion'; |
||
| 54 | const OPT_TIMEOUT = 'timeout'; |
||
| 55 | const OPT_USERNAME = 'username'; |
||
| 56 | const OPT_USER_AGENT = 'user_agent'; |
||
| 57 | const OPT_USE_CURL = 'use_curl'; |
||
| 58 | const OPT_VERIFY_HOST = 'verifyhost'; |
||
| 59 | const OPT_VERIFY_PEER = 'verifypeer'; |
||
| 60 | const OPT_EXTRA_HEADERS = 'extra_headers'; |
||
| 61 | |||
| 62 | /** @var string */ |
||
| 63 | protected static $requestClass = '\\PhpXmlRpc\\Request'; |
||
| 64 | /** @var string */ |
||
| 65 | protected static $responseClass = '\\PhpXmlRpc\\Response'; |
||
| 66 | |||
| 67 | /** |
||
| 68 | * @var int |
||
| 69 | * @deprecated will be removed in the future |
||
| 70 | */ |
||
| 71 | protected $errno; |
||
| 72 | /** |
||
| 73 | * @var string |
||
| 74 | * @deprecated will be removed in the future |
||
| 75 | */ |
||
| 76 | protected $errstr; |
||
| 77 | |||
| 78 | /// @todo: do all the ones below need to be public? |
||
| 79 | |||
| 80 | /** |
||
| 81 | * @var string |
||
| 82 | */ |
||
| 83 | protected $method = 'http'; |
||
| 84 | /** |
||
| 85 | * @var string |
||
| 86 | */ |
||
| 87 | protected $server; |
||
| 88 | /** |
||
| 89 | * @var int |
||
| 90 | */ |
||
| 91 | protected $port = 0; |
||
| 92 | /** |
||
| 93 | * @var string |
||
| 94 | */ |
||
| 95 | protected $path; |
||
| 96 | |||
| 97 | /** |
||
| 98 | * @var int |
||
| 99 | */ |
||
| 100 | protected $debug = 0; |
||
| 101 | /** |
||
| 102 | * @var string |
||
| 103 | */ |
||
| 104 | protected $username = ''; |
||
| 105 | /** |
||
| 106 | * @var string |
||
| 107 | */ |
||
| 108 | protected $password = ''; |
||
| 109 | /** |
||
| 110 | * @var int |
||
| 111 | */ |
||
| 112 | protected $authtype = 1; |
||
| 113 | /** |
||
| 114 | * @var string |
||
| 115 | */ |
||
| 116 | protected $cert = ''; |
||
| 117 | /** |
||
| 118 | * @var string |
||
| 119 | */ |
||
| 120 | protected $certpass = ''; |
||
| 121 | /** |
||
| 122 | 2 | * @var string |
|
| 123 | */ |
||
| 124 | 2 | protected $cacert = ''; |
|
| 125 | 2 | /** |
|
| 126 | * @var string |
||
| 127 | 2 | */ |
|
| 128 | protected $cacertdir = ''; |
||
| 129 | /** |
||
| 130 | * @var string |
||
| 131 | */ |
||
| 132 | protected $key = ''; |
||
| 133 | /** |
||
| 134 | * @var string |
||
| 135 | */ |
||
| 136 | protected $keypass = ''; |
||
| 137 | /** |
||
| 138 | * @var bool |
||
| 139 | */ |
||
| 140 | protected $verifypeer = true; |
||
| 141 | /** |
||
| 142 | * @var int |
||
| 143 | */ |
||
| 144 | protected $verifyhost = 2; |
||
| 145 | /** |
||
| 146 | * @var int Corresponds to CURL_SSLVERSION_DEFAULT. Other CURL_SSLVERSION_ values are supported when in curl mode, |
||
| 147 | * and in socket mode different values from 0 to 7, matching the corresponding curl value. Old php versions |
||
| 148 | * do not support all values, php 5.4 and 5.5 do not support any in fact. |
||
| 149 | * NB: please do not use any version lower than TLS 1.3 (value: 7) as they are considered insecure. |
||
| 150 | */ |
||
| 151 | protected $sslversion = 0; |
||
| 152 | 718 | /** |
|
| 153 | * @var string |
||
| 154 | */ |
||
| 155 | 718 | protected $proxy = ''; |
|
| 156 | 7 | /** |
|
| 157 | 7 | * @var int |
|
| 158 | 7 | */ |
|
| 159 | 7 | protected $proxyport = 0; |
|
| 160 | /** |
||
| 161 | * @var string |
||
| 162 | 7 | */ |
|
| 163 | protected $proxy_user = ''; |
||
| 164 | /** |
||
| 165 | 7 | * @var string |
|
| 166 | */ |
||
| 167 | protected $proxy_pass = ''; |
||
| 168 | 7 | /** |
|
| 169 | 7 | * @var int |
|
| 170 | */ |
||
| 171 | 7 | protected $proxy_authtype = 1; |
|
| 172 | /** |
||
| 173 | * @var array |
||
| 174 | 7 | */ |
|
| 175 | protected $cookies = array(); |
||
| 176 | /** |
||
| 177 | * @var array |
||
| 178 | 718 | */ |
|
| 179 | protected $extrasockopts = array(); |
||
| 180 | /** |
||
| 181 | 718 | * @var array |
|
| 182 | */ |
||
| 183 | 718 | protected $extracurlopts = array(); |
|
| 184 | 718 | /** |
|
| 185 | 3 | * @var int |
|
| 186 | */ |
||
| 187 | 718 | protected $timeout = 0; |
|
| 188 | 7 | /** |
|
| 189 | * @var int |
||
| 190 | */ |
||
| 191 | protected $use_curl = self::USE_CURL_AUTO; |
||
| 192 | 718 | /** |
|
| 193 | * @var bool |
||
| 194 | 711 | * |
|
| 195 | * This determines whether the multicall() method will try to take advantage of the system.multicall xml-rpc method |
||
| 196 | * to dispatch to the server an array of requests in a single http roundtrip or simply execute many consecutive http |
||
| 197 | 718 | * calls. Defaults to FALSE, but it will be enabled automatically on the first failure of execution of |
|
| 198 | * system.multicall. |
||
| 199 | */ |
||
| 200 | protected $no_multicall = false; |
||
| 201 | 718 | /** |
|
| 202 | * @var array |
||
| 203 | * |
||
| 204 | 718 | * List of http compression methods accepted by the client for responses. |
|
| 205 | * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib. |
||
| 206 | * |
||
| 207 | * NNB: you can set it to any non-empty array for HTTP11+ and HTTPS, since in those cases it will be up to CURL to |
||
| 208 | * decide the compression methods it supports. You might check for the presence of 'zlib' in the output of |
||
| 209 | * curl_version() to determine whether compression is supported or not |
||
| 210 | */ |
||
| 211 | protected $accepted_compression = array(); |
||
| 212 | /** |
||
| 213 | * @var string|null |
||
| 214 | * |
||
| 215 | * Name of compression scheme to be used for sending requests. |
||
| 216 | * Either null, 'gzip' or 'deflate'. |
||
| 217 | 718 | */ |
|
| 218 | 718 | protected $request_compression = ''; |
|
| 219 | /** |
||
| 220 | * @var bool |
||
| 221 | * |
||
| 222 | * Whether to use persistent connections for http 1.1 and https. Value set at constructor time. |
||
| 223 | */ |
||
| 224 | protected $keepalive = false; |
||
| 225 | /** |
||
| 226 | * @var string[] |
||
| 227 | * |
||
| 228 | * Charset encodings that can be decoded without problems by the client. Value set at constructor time |
||
| 229 | */ |
||
| 230 | protected $accepted_charset_encodings = array(); |
||
| 231 | /** |
||
| 232 | * @var string |
||
| 233 | 714 | * |
|
| 234 | * The charset encoding that will be used for serializing request sent by the client. |
||
| 235 | 714 | * It defaults to NULL, which means using US-ASCII and encoding all characters outside the ASCII printable range |
|
| 236 | 714 | * using their xml character entity representation (this has the benefit that line end characters will not be mangled |
|
| 237 | * in the transfer, a CR-LF will be preserved as well as a singe LF). |
||
| 238 | * Valid values are 'US-ASCII', 'UTF-8' and 'ISO-8859-1'. |
||
| 239 | * For the fastest mode of operation, set your both your app internal encoding and this to UTF-8. |
||
| 240 | */ |
||
| 241 | protected $request_charset_encoding = ''; |
||
| 242 | /** |
||
| 243 | * @var string |
||
| 244 | * |
||
| 245 | * Decides the content of Response objects returned by calls to send() and multicall(). |
||
| 246 | * Valid values are 'xmlrpcvals', 'phpvals' or 'xml'. |
||
| 247 | * |
||
| 248 | * Determines whether the value returned inside a Response object as results of calls to the send() and multicall() |
||
| 249 | * methods will be a Value object, a plain php value or a raw xml string. |
||
| 250 | * Allowed values are 'xmlrpcvals' (the default), 'phpvals' and 'xml'. |
||
| 251 | * To allow the user to differentiate between a correct and a faulty response, fault responses will be returned as |
||
| 252 | 66 | * Response objects in any case. |
|
| 253 | * Note that the 'phpvals' setting will yield faster execution times, but some of the information from the original |
||
| 254 | 66 | * response will be lost. It will be e.g. impossible to tell whether a particular php string value was sent by the |
|
| 255 | 66 | * server as an xml-rpc string or base64 value. |
|
| 256 | 66 | */ |
|
| 257 | 66 | protected $return_type = XMLParser::RETURN_XMLRPCVALS; |
|
| 258 | /** |
||
| 259 | * @var string |
||
| 260 | * |
||
| 261 | * Sent to servers in http headers. Value set at constructor time. |
||
| 262 | */ |
||
| 263 | protected $user_agent; |
||
| 264 | |||
| 265 | /** |
||
| 266 | * Additional headers to be included in the requests. |
||
| 267 | * |
||
| 268 | * @var string[] |
||
| 269 | */ |
||
| 270 | protected $extra_headers = array(); |
||
| 271 | |||
| 272 | /** |
||
| 273 | * CURL handle: used for keep-alive |
||
| 274 | * @internal |
||
| 275 | */ |
||
| 276 | public $xmlrpc_curl_handle = null; |
||
| 277 | |||
| 278 | /** |
||
| 279 | * @var array |
||
| 280 | */ |
||
| 281 | protected static $options = array( |
||
| 282 | self::OPT_ACCEPTED_CHARSET_ENCODINGS, |
||
| 283 | self::OPT_ACCEPTED_COMPRESSION, |
||
| 284 | self::OPT_AUTH_TYPE, |
||
| 285 | self::OPT_CA_CERT, |
||
| 286 | self::OPT_CA_CERT_DIR, |
||
| 287 | self::OPT_CERT, |
||
| 288 | self::OPT_CERT_PASS, |
||
| 289 | self::OPT_COOKIES, |
||
| 290 | self::OPT_DEBUG, |
||
| 291 | self::OPT_EXTRA_CURL_OPTS, |
||
| 292 | self::OPT_EXTRA_SOCKET_OPTS, |
||
| 293 | self::OPT_KEEPALIVE, |
||
| 294 | self::OPT_KEY, |
||
| 295 | self::OPT_KEY_PASS, |
||
| 296 | self::OPT_NO_MULTICALL, |
||
| 297 | self::OPT_PASSWORD, |
||
| 298 | self::OPT_PROXY, |
||
| 299 | self::OPT_PROXY_AUTH_TYPE, |
||
| 300 | self::OPT_PROXY_PASS, |
||
| 301 | self::OPT_PROXY_USER, |
||
| 302 | self::OPT_PROXY_PORT, |
||
| 303 | self::OPT_REQUEST_CHARSET_ENCODING, |
||
| 304 | self::OPT_REQUEST_COMPRESSION, |
||
| 305 | self::OPT_RETURN_TYPE, |
||
| 306 | self::OPT_SSL_VERSION, |
||
| 307 | self::OPT_TIMEOUT, |
||
| 308 | self::OPT_USE_CURL, |
||
| 309 | self::OPT_USER_AGENT, |
||
| 310 | self::OPT_USERNAME, |
||
| 311 | self::OPT_VERIFY_HOST, |
||
| 312 | self::OPT_VERIFY_PEER, |
||
| 313 | self::OPT_EXTRA_HEADERS, |
||
| 314 | ); |
||
| 315 | |||
| 316 | 132 | /** |
|
| 317 | * @param string $path either the PATH part of the xml-rpc server URL, or complete server URL (in which case you |
||
| 318 | 132 | * should use an empty string for all other parameters) |
|
| 319 | 132 | * e.g. /xmlrpc/server.php |
|
| 320 | * e.g. http://phpxmlrpc.sourceforge.net/server.php |
||
| 321 | * e.g. https://james:[email protected]:444/xmlrpcserver?agent=007 |
||
| 322 | * e.g. h2://fast-and-secure-services.org/endpoint |
||
| 323 | * @param string $server the server name / ip address |
||
| 324 | * @param integer $port the port the server is listening on, when omitted defaults to 80 or 443 depending on |
||
| 325 | * protocol used |
||
| 326 | * @param string $method the http protocol variant: defaults to 'http'; 'https', 'http11', 'h2' and 'h2c' can |
||
| 327 | * be used if CURL is installed. The value set here can be overridden in any call to $this->send(). |
||
| 328 | 132 | * Use 'h2' to make the lib attempt to use http/2 over a secure connection, and 'h2c' |
|
| 329 | * for http/2 without tls. Note that 'h2c' will not use the h2c 'upgrade' method, and be |
||
| 330 | 132 | * thus incompatible with any server/proxy not supporting http/2. This is because POST |
|
| 331 | 132 | * request are not compatible with h2c upgrade. |
|
| 332 | */ |
||
| 333 | public function __construct($path, $server = '', $port = '', $method = '') |
||
| 334 | { |
||
| 335 | // allow user to specify all params in $path |
||
| 336 | if ($server == '' && $port == '' && $method == '') { |
||
| 337 | $parts = parse_url($path); |
||
| 338 | 132 | $server = $parts['host']; |
|
| 339 | $path = isset($parts['path']) ? $parts['path'] : ''; |
||
| 340 | 132 | if (isset($parts['query'])) { |
|
| 341 | 132 | $path .= '?' . $parts['query']; |
|
| 342 | } |
||
| 343 | if (isset($parts['fragment'])) { |
||
| 344 | $path .= '#' . $parts['fragment']; |
||
| 345 | } |
||
| 346 | if (isset($parts['port'])) { |
||
| 347 | $port = $parts['port']; |
||
| 348 | } |
||
| 349 | if (isset($parts['scheme'])) { |
||
| 350 | $method = $parts['scheme']; |
||
| 351 | } |
||
| 352 | if (isset($parts['user'])) { |
||
| 353 | $this->username = $parts['user']; |
||
| 354 | } |
||
| 355 | 99 | if (isset($parts['pass'])) { |
|
| 356 | $this->password = $parts['pass']; |
||
| 357 | 99 | } |
|
| 358 | 99 | } |
|
| 359 | 99 | if ($path == '' || $path[0] != '/') { |
|
| 360 | 99 | $this->path = '/' . $path; |
|
| 361 | 99 | } else { |
|
| 362 | 99 | $this->path = $path; |
|
| 363 | } |
||
| 364 | $this->server = $server; |
||
| 365 | if ($port != '') { |
||
| 366 | $this->port = $port; |
||
| 367 | } |
||
| 368 | if ($method != '') { |
||
| 369 | $this->method = $method; |
||
| 370 | } |
||
| 371 | |||
| 372 | // if ZLIB is enabled, let the client by default accept compressed responses |
||
| 373 | if (function_exists('gzinflate') || ( |
||
| 374 | function_exists('curl_version') && (($info = curl_version()) && |
||
| 375 | ((is_string($info) && strpos($info, 'zlib') !== null) || isset($info['libz_version']))) |
||
| 376 | ) |
||
| 377 | ) { |
||
| 378 | $this->accepted_compression = array('gzip', 'deflate'); |
||
| 379 | } |
||
| 380 | |||
| 381 | // keepalives: enabled by default |
||
| 382 | $this->keepalive = true; |
||
| 383 | |||
| 384 | // by default the xml parser can support these 3 charset encodings |
||
| 385 | $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII'); |
||
| 386 | |||
| 387 | // NB: this is disabled to avoid making all the requests sent huge... mbstring supports more than 80 charsets! |
||
| 388 | //$ch = $this->getCharsetEncoder(); |
||
| 389 | //$this->accepted_charset_encodings = $ch->knownCharsets(); |
||
| 390 | |||
| 391 | // initialize user_agent string |
||
| 392 | $this->user_agent = PhpXmlRpc::$xmlrpcName . ' ' . PhpXmlRpc::$xmlrpcVersion; |
||
| 393 | } |
||
| 394 | |||
| 395 | /** |
||
| 396 | * @param string $name see all the OPT_ constants |
||
| 397 | * @param mixed $value |
||
| 398 | * @return $this |
||
| 399 | * @throws ValueErrorException on unsupported option |
||
| 400 | */ |
||
| 401 | public function setOption($name, $value) |
||
| 402 | { |
||
| 403 | if (in_array($name, static::$options)) { |
||
| 404 | $this->$name = $value; |
||
| 405 | return $this; |
||
| 406 | } |
||
| 407 | |||
| 408 | throw new ValueErrorException("Unsupported option '$name'"); |
||
| 409 | } |
||
| 410 | |||
| 411 | /** |
||
| 412 | * @param string $name see all the OPT_ constants |
||
| 413 | * @return mixed |
||
| 414 | * @throws ValueErrorException on unsupported option |
||
| 415 | */ |
||
| 416 | public function getOption($name) |
||
| 417 | { |
||
| 418 | 580 | if (in_array($name, static::$options)) { |
|
| 419 | return $this->$name; |
||
| 420 | 580 | } |
|
| 421 | 580 | ||
| 422 | throw new ValueErrorException("Unsupported option '$name'"); |
||
| 423 | } |
||
| 424 | |||
| 425 | /** |
||
| 426 | * Returns the complete list of Client options, with their value. |
||
| 427 | 580 | * @return array |
|
| 428 | */ |
||
| 429 | 580 | public function getOptions() |
|
| 430 | { |
||
| 431 | $values = array(); |
||
| 432 | foreach (static::$options as $opt) { |
||
| 433 | $values[$opt] = $this->getOption($opt); |
||
| 434 | } |
||
| 435 | return $values; |
||
| 436 | } |
||
| 437 | |||
| 438 | /** |
||
| 439 | * @param array $options key: any valid option (see all the OPT_ constants) |
||
| 440 | * @return $this |
||
| 441 | * @throws ValueErrorException on unsupported option |
||
| 442 | */ |
||
| 443 | public function setOptions($options) |
||
| 444 | { |
||
| 445 | foreach ($options as $name => $value) { |
||
| 446 | 66 | $this->setOption($name, $value); |
|
| 447 | } |
||
| 448 | 66 | ||
| 449 | 66 | return $this; |
|
| 450 | } |
||
| 451 | |||
| 452 | /** |
||
| 453 | * Enable/disable the echoing to screen of the xml-rpc responses received. The default is not to output anything. |
||
| 454 | * |
||
| 455 | * The debugging information at level 1 includes the raw data returned from the XML-RPC server it was querying |
||
| 456 | * (including bot HTTP headers and the full XML payload), and the PHP value the client attempts to create to |
||
| 457 | * represent the value returned by the server. |
||
| 458 | * At level 2, the complete payload of the xml-rpc request is also printed, before being sent to the server. |
||
| 459 | * At level -1, the Response objects returned by send() calls will not carry information about the http response's |
||
| 460 | * cookies, headers and body, which might save some memory |
||
| 461 | * |
||
| 462 | * This option can be very useful when debugging servers as it allows you to see exactly what the client sends and |
||
| 463 | * the server returns. Never leave it enabled for production! |
||
| 464 | * |
||
| 465 | * @param integer $level values -1, 0, 1 and 2 are supported |
||
| 466 | * @return $this |
||
| 467 | */ |
||
| 468 | public function setDebug($level) |
||
| 469 | { |
||
| 470 | $this->debug = $level; |
||
| 471 | return $this; |
||
| 472 | } |
||
| 473 | |||
| 474 | /** |
||
| 475 | * Sets the username and password for authorizing the client to the server. |
||
| 476 | * |
||
| 477 | * With the default (HTTP) transport, this information is used for HTTP Basic authorization. |
||
| 478 | * Note that username and password can also be set using the class constructor. |
||
| 479 | * With HTTP 1.1 and HTTPS transport, NTLM and Digest authentication protocols are also supported. To enable them use |
||
| 480 | * the constants CURLAUTH_DIGEST and CURLAUTH_NTLM as values for the auth type parameter. |
||
| 481 | * |
||
| 482 | * @param string $user username |
||
| 483 | * @param string $password password |
||
| 484 | * @param integer $authType auth type. See curl_setopt man page for supported auth types. Defaults to CURLAUTH_BASIC |
||
| 485 | * (basic auth). Note that auth types NTLM and Digest will only work if the Curl php |
||
| 486 | * extension is enabled. |
||
| 487 | * @return $this |
||
| 488 | */ |
||
| 489 | public function setCredentials($user, $password, $authType = 1) |
||
| 490 | { |
||
| 491 | $this->username = $user; |
||
| 492 | $this->password = $password; |
||
| 493 | $this->authtype = $authType; |
||
| 494 | return $this; |
||
| 495 | 697 | } |
|
| 496 | |||
| 497 | /** |
||
| 498 | * Set the optional certificate and passphrase used in SSL-enabled communication with a remote server. |
||
| 499 | 697 | * |
|
| 500 | 117 | * Note: to retrieve information about the client certificate on the server side, you will need to look into the |
|
| 501 | * environment variables which are set up by the webserver. Different webservers will typically set up different |
||
| 502 | * variables. |
||
| 503 | 697 | * |
|
| 504 | * @param string $cert the name of a file containing a PEM formatted certificate |
||
| 505 | 66 | * @param string $certPass the password required to use it |
|
| 506 | * @return $this |
||
| 507 | 66 | */ |
|
| 508 | 697 | public function setCertificate($cert, $certPass = '') |
|
| 509 | 28 | { |
|
| 510 | 28 | $this->cert = $cert; |
|
| 511 | 28 | $this->certpass = $certPass; |
|
| 512 | return $this; |
||
| 513 | } |
||
| 514 | |||
| 515 | 697 | /** |
|
| 516 | * Add a CA certificate to verify server with in SSL-enabled communication when SetSSLVerifypeer has been set to TRUE. |
||
| 517 | * |
||
| 518 | * See the php manual page about CURLOPT_CAINFO for more details. |
||
| 519 | 697 | * |
|
| 520 | 697 | * @param string $caCert certificate file name (or dir holding certificates) |
|
| 521 | * @param bool $isDir set to true to indicate cacert is a dir. defaults to false |
||
| 522 | 697 | * @return $this |
|
| 523 | 322 | */ |
|
| 524 | 322 | public function setCaCertificate($caCert, $isDir = false) |
|
| 525 | 322 | { |
|
| 526 | 322 | if ($isDir) { |
|
| 527 | $this->cacertdir = $caCert; |
||
| 528 | 322 | } else { |
|
| 529 | 322 | $this->cacert = $caCert; |
|
| 530 | 322 | } |
|
| 531 | 322 | return $this; |
|
| 532 | 322 | } |
|
| 533 | 322 | ||
| 534 | 322 | /** |
|
| 535 | 322 | * Set attributes for SSL communication: private SSL key. |
|
| 536 | 322 | * |
|
| 537 | 322 | * NB: does not work in older php/curl installs. |
|
| 538 | 322 | * Thanks to Daniel Convissor. |
|
| 539 | 322 | * |
|
| 540 | * @param string $key The name of a file containing a private SSL key |
||
| 541 | 322 | * @param string $keyPass The secret password needed to use the private SSL key |
|
| 542 | 322 | * @return $this |
|
| 543 | 322 | */ |
|
| 544 | 322 | public function setKey($key, $keyPass) |
|
| 545 | 322 | { |
|
| 546 | $this->key = $key; |
||
| 547 | $this->keypass = $keyPass; |
||
| 548 | return $this; |
||
| 549 | 375 | } |
|
| 550 | 375 | ||
| 551 | 375 | /** |
|
| 552 | 375 | * Set attributes for SSL communication: verify the remote host's SSL certificate, and cause the connection to fail |
|
| 553 | * if the cert verification fails. |
||
| 554 | 375 | * |
|
| 555 | 375 | * By default, verification is enabled. |
|
| 556 | 375 | * To specify custom SSL certificates to validate the server with, use the setCaCertificate method. |
|
| 557 | 375 | * |
|
| 558 | 375 | * @param bool $i enable/disable verification of peer certificate |
|
| 559 | 375 | * @return $this |
|
| 560 | 375 | * @deprecated use setOption |
|
| 561 | 375 | */ |
|
| 562 | 375 | public function setSSLVerifyPeer($i) |
|
| 563 | 375 | { |
|
| 564 | 375 | $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated'); |
|
| 565 | 375 | ||
| 566 | $this->verifypeer = $i; |
||
| 567 | 375 | return $this; |
|
| 568 | 375 | } |
|
| 569 | 375 | ||
| 570 | /** |
||
| 571 | * Set attributes for SSL communication: verify the remote host's SSL certificate's common name (CN). |
||
| 572 | * |
||
| 573 | 697 | * Note that support for value 1 has been removed in cURL 7.28.1 |
|
| 574 | * |
||
| 575 | * @param int $i Set to 1 to only the existence of a CN, not that it matches |
||
| 576 | * @return $this |
||
| 577 | * @deprecated use setOption |
||
| 578 | */ |
||
| 579 | public function setSSLVerifyHost($i) |
||
| 580 | { |
||
| 581 | $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated'); |
||
| 582 | |||
| 583 | $this->verifyhost = $i; |
||
| 584 | return $this; |
||
| 585 | } |
||
| 586 | |||
| 587 | /** |
||
| 588 | * Set attributes for SSL communication: SSL version to use. Best left at 0 (default value): let PHP decide. |
||
| 589 | * |
||
| 590 | * @param int $i use CURL_SSLVERSION_ constants. When in socket mode, use the same values: 2 (SSLv2) to 7 (TLSv1.3), |
||
| 591 | * 0 for auto (note that old php versions do not support all TLS versions). |
||
| 592 | * Note that, in curl mode, the actual ssl version in use might be higher than requested. |
||
| 593 | * NB: please do not use any version lower than TLS 1.3 as they are considered insecure. |
||
| 594 | * @return $this |
||
| 595 | * @deprecated use setOption |
||
| 596 | */ |
||
| 597 | public function setSSLVersion($i) |
||
| 598 | { |
||
| 599 | $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated'); |
||
| 600 | |||
| 601 | $this->sslversion = $i; |
||
| 602 | return $this; |
||
| 603 | } |
||
| 604 | |||
| 605 | /** |
||
| 606 | * Set proxy info. |
||
| 607 | * |
||
| 608 | * NB: CURL versions before 7.11.10 cannot use a proxy to communicate with https servers. |
||
| 609 | * |
||
| 610 | * @param string $proxyHost |
||
| 611 | * @param string $proxyPort Defaults to 8080 for HTTP and 443 for HTTPS |
||
| 612 | * @param string $proxyUsername Leave blank if proxy has public access |
||
| 613 | * @param string $proxyPassword Leave blank if proxy has public access |
||
| 614 | * @param int $proxyAuthType defaults to CURLAUTH_BASIC (Basic authentication protocol); set to constant CURLAUTH_NTLM |
||
| 615 | * to use NTLM auth with proxy (has effect only when the client uses the HTTP 1.1 protocol) |
||
| 616 | * @return $this |
||
| 617 | */ |
||
| 618 | public function setProxy($proxyHost, $proxyPort, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1) |
||
| 619 | { |
||
| 620 | $this->proxy = $proxyHost; |
||
| 621 | $this->proxyport = $proxyPort; |
||
| 622 | $this->proxy_user = $proxyUsername; |
||
| 623 | $this->proxy_pass = $proxyPassword; |
||
| 624 | $this->proxy_authtype = $proxyAuthType; |
||
| 625 | return $this; |
||
| 626 | } |
||
| 627 | |||
| 628 | /** |
||
| 629 | * Enables/disables reception of compressed xml-rpc responses. |
||
| 630 | * |
||
| 631 | * This requires the "zlib" extension to be enabled in your php install. If it is, by default xmlrpc_client |
||
| 632 | * instances will enable reception of compressed content. |
||
| 633 | * Note that enabling reception of compressed responses merely adds some standard http headers to xml-rpc requests. |
||
| 634 | * It is up to the xml-rpc server to return compressed responses when receiving such requests. |
||
| 635 | * |
||
| 636 | * @param string $compMethod either 'gzip', 'deflate', 'any' or '' |
||
| 637 | * @return $this |
||
| 638 | */ |
||
| 639 | public function setAcceptedCompression($compMethod) |
||
| 640 | { |
||
| 641 | if ($compMethod == 'any') { |
||
| 642 | $this->accepted_compression = array('gzip', 'deflate'); |
||
| 643 | } elseif ($compMethod == false) { |
||
| 644 | $this->accepted_compression = array(); |
||
| 645 | } else { |
||
| 646 | $this->accepted_compression = array($compMethod); |
||
| 647 | } |
||
| 648 | return $this; |
||
| 649 | } |
||
| 650 | |||
| 651 | /** |
||
| 652 | * Enables/disables http compression of xml-rpc request. |
||
| 653 | * |
||
| 654 | * This requires the "zlib" extension to be enabled in your php install. |
||
| 655 | * Take care when sending compressed requests: servers might not support them (and automatic fallback to |
||
| 656 | * uncompressed requests is not yet implemented). |
||
| 657 | * |
||
| 658 | * @param string $compMethod either 'gzip', 'deflate' or '' |
||
| 659 | * @return $this |
||
| 660 | * @deprecated use setOption |
||
| 661 | */ |
||
| 662 | public function setRequestCompression($compMethod) |
||
| 663 | { |
||
| 664 | 375 | $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated'); |
|
| 665 | |||
| 666 | $this->request_compression = $compMethod; |
||
| 667 | return $this; |
||
| 668 | } |
||
| 669 | |||
| 670 | /** |
||
| 671 | 375 | * Adds a cookie to list of cookies that will be sent to server with every further request (useful e.g. for keeping |
|
| 672 | 373 | * session info outside the xml-rpc payload). |
|
| 673 | * |
||
| 674 | * NB: by default all cookies set via this method are sent to the server, regardless of path/domain/port. Taking |
||
| 675 | * advantage of those values is left to the single developer. |
||
| 676 | 375 | * |
|
| 677 | 346 | * @param string $name nb: will not be escaped in the request's http headers. Take care not to use CTL chars or |
|
| 678 | * separators! |
||
| 679 | * @param string $value |
||
| 680 | 375 | * @param string $path |
|
| 681 | * @param string $domain |
||
| 682 | 375 | * @param int $port do not use! Cookies are not separated by port |
|
| 683 | 375 | * @return $this |
|
| 684 | 64 | * |
|
| 685 | 32 | * @todo check correctness of urlencoding cookie value (copied from php way of doing it, but php is generally sending |
|
| 686 | 32 | * response not requests. We do the opposite...) |
|
| 687 | 32 | * @todo strip invalid chars from cookie name? As per RFC 6265, we should follow RFC 2616, Section 2.2 |
|
| 688 | 32 | * @todo drop/rename $port parameter. Cookies are not isolated by port! |
|
| 689 | * @todo feature-creep allow storing 'expires', 'secure', 'httponly' and 'samesite' cookie attributes (we could do |
||
| 690 | * as php, and allow $path to be an array of attributes...) |
||
| 691 | 32 | */ |
|
| 692 | 32 | public function setCookie($name, $value = '', $path = '', $domain = '', $port = null) |
|
| 693 | 32 | { |
|
| 694 | 32 | $this->cookies[$name]['value'] = rawurlencode($value); |
|
| 695 | if ($path || $domain || $port) { |
||
| 696 | $this->cookies[$name]['path'] = $path; |
||
| 697 | $this->cookies[$name]['domain'] = $domain; |
||
| 698 | $this->cookies[$name]['port'] = $port; |
||
| 699 | } |
||
| 700 | 375 | return $this; |
|
| 701 | 375 | } |
|
| 702 | 32 | ||
| 703 | 32 | /** |
|
| 704 | * Directly set cURL options, for extra flexibility (when in cURL mode). |
||
| 705 | * |
||
| 706 | * It allows e.g. to bind client to a specific IP interface / address. |
||
| 707 | * |
||
| 708 | 375 | * @param array $options |
|
| 709 | 375 | * @return $this |
|
| 710 | 73 | * @deprecated use setOption |
|
| 711 | */ |
||
| 712 | public function setCurlOptions($options) |
||
| 713 | 375 | { |
|
| 714 | 375 | $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated'); |
|
| 715 | 32 | ||
| 716 | $this->extracurlopts = $options; |
||
| 717 | return $this; |
||
| 718 | 32 | } |
|
| 719 | 32 | ||
| 720 | 32 | /** |
|
| 721 | 32 | * @param int $useCurlMode self::USE_CURL_ALWAYS, self::USE_CURL_AUTO or self::USE_CURL_NEVER |
|
| 722 | 32 | * In 'auto' mode, curl is picked up based on features used, such as fe. NTLM auth, or https |
|
| 723 | * @return $this |
||
| 724 | * @deprecated use setOption |
||
| 725 | */ |
||
| 726 | 32 | public function setUseCurl($useCurlMode) |
|
| 727 | { |
||
| 728 | $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated'); |
||
| 729 | 343 | ||
| 730 | 343 | $this->use_curl = $useCurlMode; |
|
| 731 | 343 | return $this; |
|
| 732 | 343 | } |
|
| 733 | |||
| 734 | /** |
||
| 735 | * Set user-agent string that will be used by this client instance in http headers sent to the server. |
||
| 736 | 375 | * |
|
| 737 | 375 | * The default user agent string includes the name of this library and the version number. |
|
| 738 | 309 | * |
|
| 739 | 309 | * @param string $agentString |
|
| 740 | 309 | * @return $this |
|
| 741 | * @deprecated use setOption |
||
| 742 | */ |
||
| 743 | public function setUserAgent($agentString) |
||
| 744 | { |
||
| 745 | $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated'); |
||
| 746 | |||
| 747 | $this->user_agent = $agentString; |
||
| 748 | return $this; |
||
| 749 | } |
||
| 750 | |||
| 751 | /** |
||
| 752 | * @param null|int $component allowed values: PHP_URL_SCHEME, PHP_URL_HOST, PHP_URL_PORT, PHP_URL_PATH |
||
| 753 | 309 | * @return string|int Notes: the path component will include query string and fragment; NULL is a valid value for port |
|
| 754 | * (in which case the default port for http/https will be used); the url scheme component will |
||
| 755 | * reflect the `$method` used in the constructor, so it might not be http or https |
||
| 756 | 309 | * @throws ValueErrorException on unsupported component |
|
| 757 | */ |
||
| 758 | public function getUrl($component = null) |
||
| 759 | { |
||
| 760 | 375 | if (is_int($component) || ($component !== null && ctype_digit($component))) { |
|
| 761 | 375 | switch ($component) { |
|
| 762 | case PHP_URL_SCHEME: |
||
| 763 | return $this->method; |
||
| 764 | case PHP_URL_HOST: |
||
| 765 | return $this->server; |
||
| 766 | 375 | case PHP_URL_PORT: |
|
| 767 | 375 | return $this->port; |
|
| 768 | 375 | case PHP_URL_PATH: |
|
| 769 | 375 | return $this->path; |
|
| 770 | 375 | case '': |
|
| 771 | 375 | ||
| 772 | 375 | default: |
|
| 773 | 375 | throw new ValueErrorException("Unsupported component '$component'"); |
|
| 774 | 375 | } |
|
| 775 | 375 | } |
|
| 776 | 375 | ||
| 777 | 375 | $url = $this->method . '://' . $this->server; |
|
| 778 | if ($this->port == 0 || ($this->port == 80 && in_array($this->method, array('http', 'http10', 'http11', 'http11_only', 'h2c'))) || |
||
| 779 | 375 | ($this->port == 443 && in_array($this->method, array('https', 'h2')))) { |
|
| 780 | 2 | return $url . $this->path; |
|
| 781 | } else { |
||
| 782 | return $url . ':' . $this->port . $this->path; |
||
| 783 | 375 | } |
|
| 784 | 375 | } |
|
| 785 | 32 | ||
| 786 | /** |
||
| 787 | * Send an xml-rpc request to the server. |
||
| 788 | * |
||
| 789 | * @param Request|Request[]|string $req The Request object, or an array of requests for using multicall, or the |
||
| 790 | * complete xml representation of a request. |
||
| 791 | 32 | * When sending an array of Request objects, the client will try to make use of |
|
| 792 | * a single 'system.multicall' xml-rpc method call to forward to the server all |
||
| 793 | * the requests in a single HTTP round trip, unless $this->no_multicall has |
||
| 794 | 32 | * been previously set to TRUE (see the multicall method below), in which case |
|
| 795 | * many consecutive xml-rpc requests will be sent. The method will return an |
||
| 796 | * array of Response objects in both cases. |
||
| 797 | 32 | * The third variant allows to build by hand (or any other means) a complete |
|
| 798 | * xml-rpc request message, and send it to the server. $req should be a string |
||
| 799 | * containing the complete xml representation of the request. It is e.g. useful |
||
| 800 | 32 | * when, for maximal speed of execution, the request is serialized into a |
|
| 801 | 32 | * string using the native php xml-rpc functions (see http://www.php.net/xmlrpc) |
|
| 802 | * @param integer $timeout deprecated. Connection timeout, in seconds, If unspecified, the timeout set with setOption |
||
| 803 | * will be used. If that is 0, a platform specific timeout will apply. |
||
| 804 | 375 | * This timeout value is passed to fsockopen(). It is also used for detecting server |
|
| 805 | * timeouts during communication (i.e. if the server does not send anything to the client |
||
| 806 | 375 | * for $timeout seconds, the connection will be closed). When in CURL mode, this is the |
|
| 807 | 64 | * CURL timeout. |
|
| 808 | * NB: in both CURL and Socket modes, some conditions might lead to the client not |
||
| 809 | 311 | * respecting the given timeout. Eg. if the network is not connected |
|
| 810 | * @param string $method deprecated. Use the same value in the constructor instead. |
||
| 811 | * Valid values are 'http', 'http11', 'https', 'h2' and 'h2c'. If left empty, |
||
| 812 | 375 | * the http protocol chosen during creation of the object will be used. |
|
| 813 | 375 | * Use 'h2' to make the lib attempt to use http/2 over a secure connection, and 'h2c' |
|
| 814 | * for http/2 without tls. Note that 'h2c' will not use the h2c 'upgrade' method, and be |
||
| 815 | 375 | * thus incompatible with any server/proxy not supporting http/2. This is because POST |
|
| 816 | 375 | * request are not compatible with h2c upgrade. |
|
| 817 | 375 | * You can also use 'http11_only' to force usage of curl with http 1.1 (no http2) |
|
| 818 | 374 | * @return Response|Response[] Note that the client will always return a Response object, even if the call fails |
|
| 819 | 367 | * |
|
| 820 | * @todo allow throwing exceptions instead of returning responses in case of failed calls and/or Fault responses |
||
| 821 | * @todo refactor: we now support many options besides connection timeout and http version to use. Why only privilege those? |
||
| 822 | 1 | */ |
|
| 823 | public function send($req, $timeout = 0, $method = '') |
||
| 824 | { |
||
| 825 | if ($method !== '' || $timeout !== 0) { |
||
| 826 | $this->logDeprecation("Using non-default values for arguments 'method' and 'timeout' when calling method " . __METHOD__ . ' is deprecated'); |
||
| 827 | 1 | } |
|
| 828 | 1 | ||
| 829 | // if user does not specify http protocol, use native method of this client |
||
| 830 | 1 | // (i.e. method set during call to constructor) |
|
| 831 | if ($method == '') { |
||
| 832 | $method = $this->method; |
||
| 833 | 374 | } |
|
| 834 | |||
| 835 | if ($timeout == 0) { |
||
| 836 | $timeout = $this->timeout; |
||
| 837 | } |
||
| 838 | |||
| 839 | if (is_array($req)) { |
||
| 840 | // $req is an array of Requests |
||
| 841 | /// @todo switch to the new syntax for multicall |
||
| 842 | return $this->multicall($req, $timeout, $method); |
||
| 843 | 374 | } elseif (is_string($req)) { |
|
| 844 | $n = new static::$requestClass(''); |
||
| 845 | /// @todo we should somehow allow the caller to declare a custom contenttype too, esp. for the charset declaration |
||
| 846 | $n->setPayload($req); |
||
| 847 | 374 | $req = $n; |
|
| 848 | 374 | } |
|
| 849 | 374 | ||
| 850 | // where req is a Request |
||
| 851 | 374 | $req->setDebug($this->debug); |
|
| 852 | |||
| 853 | 374 | /// @todo move to its own function, to make it easier to change in subclasses |
|
| 854 | /// @todo we could be smarter about this: |
||
| 855 | /// - not force usage of curl if it is not present |
||
| 856 | /// - not force usage of curl for https (minor BC) |
||
| 857 | /// - use the presence of curl_extra_opts or socket_extra_opts as a hint |
||
| 858 | $useCurl = ($this->use_curl == self::USE_CURL_ALWAYS) || ($this->use_curl == self::USE_CURL_AUTO && ( |
||
| 859 | in_array($method, array('https', 'http11', 'http11_only', 'h2c', 'h2')) || |
||
| 860 | ($this->username != '' && $this->authtype != 1) || |
||
| 861 | ($this->proxy != '' && $this->proxy_user != '' && $this->proxy_authtype != 1) |
||
| 862 | // uncomment the following if not forcing curl always for 'https' |
||
| 863 | //|| ($this->sslversion == 7 && PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION == '7.3') |
||
| 864 | //|| ($this->sslversion != 0 && PHP_MAJOR_VERSION < 6) |
||
| 865 | )); |
||
| 866 | |||
| 867 | // BC - we go through sendPayloadCURL/sendPayloadSocket in case some subclass reimplemented those |
||
| 868 | if ($useCurl) { |
||
| 869 | $r = $this->sendPayloadCURL( |
||
| 870 | $req, |
||
| 871 | $this->server, |
||
| 872 | $this->port, |
||
| 873 | $timeout, |
||
| 874 | $this->username, |
||
| 875 | $this->password, |
||
| 876 | $this->authtype, |
||
| 877 | $this->cert, |
||
| 878 | $this->certpass, |
||
| 879 | $this->cacert, |
||
| 880 | $this->cacertdir, |
||
| 881 | $this->proxy, |
||
| 882 | $this->proxyport, |
||
| 883 | $this->proxy_user, |
||
| 884 | $this->proxy_pass, |
||
| 885 | $this->proxy_authtype, |
||
| 886 | 322 | // BC - http11 was used to force enabling curl |
|
| 887 | $method == 'http11' ? 'http' : ($method == 'http11_only' ? 'http11' : $method), |
||
| 888 | $this->keepalive, |
||
| 889 | $this->key, |
||
| 890 | $this->keypass, |
||
| 891 | 322 | $this->sslversion |
|
| 892 | ); |
||
| 893 | } else { |
||
| 894 | $r = $this->sendPayloadSocket( |
||
| 895 | 322 | $req, |
|
| 896 | $this->server, |
||
| 897 | 96 | $this->port, |
|
| 898 | 96 | $timeout, |
|
| 899 | $this->username, |
||
| 900 | $this->password, |
||
| 901 | $this->authtype, |
||
| 902 | $this->cert, |
||
| 903 | $this->certpass, |
||
| 904 | 322 | $this->cacert, |
|
| 905 | 322 | $this->cacertdir, |
|
| 906 | $this->proxy, |
||
| 907 | $this->proxyport, |
||
| 908 | $this->proxy_user, |
||
| 909 | $this->proxy_pass, |
||
| 910 | 322 | $this->proxy_authtype, |
|
| 911 | $method == 'http11_only' ? 'http11' : $method, |
||
| 912 | $this->key, |
||
| 913 | $this->keypass, |
||
| 914 | $this->sslversion |
||
| 915 | 322 | ); |
|
| 916 | } |
||
| 917 | 322 | ||
| 918 | return $r; |
||
| 919 | } |
||
| 920 | |||
| 921 | /** |
||
| 922 | * @param Request $req |
||
| 923 | * @param string $method |
||
| 924 | * @param string $server |
||
| 925 | * @param int $port |
||
| 926 | * @param string $path |
||
| 927 | * @param array $opts |
||
| 928 | * @return Response |
||
| 929 | 322 | */ |
|
| 930 | protected function sendViaSocket($req, $method, $server, $port, $path, $opts) |
||
| 931 | { |
||
| 932 | 1 | /// @todo log a warning if passed an unsupported method |
|
| 933 | 1 | ||
| 934 | 1 | // Only create the payload if it was not created previously |
|
| 935 | 1 | /// @todo what if the request's payload was created with a different encoding? |
|
| 936 | 1 | /// Also, if we do not call serialize(), the request will not set its content-type to have the charset declared |
|
| 937 | $payload = $req->getPayload(); |
||
| 938 | if (empty($payload)) { |
||
| 939 | 322 | $payload = $req->serialize($opts['request_charset_encoding']); |
|
| 940 | 160 | } |
|
| 941 | |||
| 942 | 322 | // Deflate request body and set appropriate request headers |
|
| 943 | $encodingHdr = ''; |
||
| 944 | 322 | if ($opts['request_compression'] == 'gzip' || $opts['request_compression'] == 'deflate') { |
|
| 945 | if ($opts['request_compression'] == 'gzip' && function_exists('gzencode')) { |
||
| 946 | $a = @gzencode($payload); |
||
| 947 | if ($a) { |
||
| 948 | $payload = $a; |
||
| 949 | $encodingHdr = "Content-Encoding: gzip\r\n"; |
||
| 950 | 322 | } else { |
|
| 951 | $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzencode failure in compressing request'); |
||
| 952 | } |
||
| 953 | 323 | } else if (function_exists('gzcompress')) { |
|
| 954 | $a = @gzcompress($payload); |
||
| 955 | if ($a) { |
||
| 956 | $payload = $a; |
||
| 957 | $encodingHdr = "Content-Encoding: deflate\r\n"; |
||
| 958 | 323 | } else { |
|
| 959 | 322 | $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzcompress failure in compressing request'); |
|
| 960 | 226 | } |
|
| 961 | } else { |
||
| 962 | 96 | $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported by this PHP install'); |
|
| 963 | } |
||
| 964 | } else { |
||
| 965 | if ($opts['request_compression'] != '') { |
||
| 966 | $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported'); |
||
| 967 | 323 | } |
|
| 968 | 302 | } |
|
| 969 | |||
| 970 | // thanks to Grant Rauscher |
||
| 971 | $credentials = ''; |
||
| 972 | 323 | if ($opts['username'] != '') { |
|
| 973 | 323 | if ($opts['authtype'] != 1) { |
|
| 974 | 64 | $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported with HTTP 1.0'); |
|
| 975 | 32 | return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'], |
|
| 976 | 32 | PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': only Basic auth is supported with HTTP 1.0'); |
|
| 977 | 32 | } |
|
| 978 | 32 | $credentials = 'Authorization: Basic ' . base64_encode($opts['username'] . ':' . $opts['password']) . "\r\n"; |
|
| 979 | } |
||
| 980 | |||
| 981 | 32 | $acceptedEncoding = ''; |
|
| 982 | 32 | if (is_array($opts['accepted_compression']) && count($opts['accepted_compression'])) { |
|
| 983 | 32 | $acceptedEncoding = 'Accept-Encoding: ' . implode(', ', $opts['accepted_compression']) . "\r\n"; |
|
| 984 | 64 | } |
|
| 985 | |||
| 986 | if ($port == 0) { |
||
| 987 | $port = ($method === 'https') ? 443 : 80; |
||
| 988 | 259 | } |
|
| 989 | |||
| 990 | $proxyCredentials = ''; |
||
| 991 | 323 | if ($opts['proxy']) { |
|
| 992 | 323 | if ($opts['proxyport'] == 0) { |
|
| 993 | 64 | $opts['proxyport'] = 8080; |
|
| 994 | } |
||
| 995 | 259 | $connectServer = $opts['proxy']; |
|
| 996 | 32 | $connectPort = $opts['proxyport']; |
|
| 997 | $transport = 'tcp'; |
||
| 998 | $protocol = $method; |
||
| 999 | 227 | if ($method === 'http10' || $method === 'http11') { |
|
| 1000 | $protocol = 'http'; |
||
| 1001 | } elseif ($method === 'h2') { |
||
| 1002 | 323 | $protocol = 'https'; |
|
| 1003 | 323 | } else if (strpos($protocol, ':') !== false) { |
|
| 1004 | 322 | $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ": warning - attempted hacking attempt?. The protocol requested for the call is: '$protocol'"); |
|
| 1005 | return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'], PhpXmlRpc::$xmlrpcerr['unsupported_option'] . |
||
| 1006 | " attempted hacking attempt?. The protocol requested for the call is: '$protocol'"); |
||
| 1007 | 56 | } |
|
| 1008 | /// @todo this does not work atm (tested at least with an http proxy forwarding to an https server) - we |
||
| 1009 | /// should implement the CONNECT protocol |
||
| 1010 | $uri = $protocol . '://' . $server . ':' . $port . $path; |
||
| 1011 | 323 | if ($opts['proxy_user'] != '') { |
|
| 1012 | if ($opts['proxy_authtype'] != 1) { |
||
| 1013 | 323 | $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported with HTTP 1.0'); |
|
| 1014 | return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'], |
||
| 1015 | PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': only Basic auth to proxy is supported with socket transport'); |
||
| 1016 | } |
||
| 1017 | 323 | $proxyCredentials = 'Proxy-Authorization: Basic ' . base64_encode($opts['proxy_user'] . ':' . |
|
| 1018 | $opts['proxy_pass']) . "\r\n"; |
||
| 1019 | 323 | } |
|
| 1020 | } else { |
||
| 1021 | 323 | $connectServer = $server; |
|
| 1022 | $connectPort = $port; |
||
| 1023 | /// @todo should we add support for 'h2' method? If so, is it 'tls' or 'tcp' ? |
||
| 1024 | 323 | $transport = ($method === 'https') ? 'tls' : 'tcp'; |
|
| 1025 | $uri = $path; |
||
| 1026 | } |
||
| 1027 | |||
| 1028 | 323 | // Cookie generation, as per RFC 6265 |
|
| 1029 | // NB: the following code does not honour 'expires', 'path' and 'domain' cookie attributes set to client obj by the user... |
||
| 1030 | $cookieHeader = ''; |
||
| 1031 | 66 | if (count($opts['cookies'])) { |
|
| 1032 | 64 | $version = ''; |
|
| 1033 | foreach ($opts['cookies'] as $name => $cookie) { |
||
| 1034 | 2 | /// @todo should we sanitize the cookie value on behalf of the user? See setCookie comments |
|
| 1035 | $cookieHeader .= ' ' . $name . '=' . $cookie['value'] . ";"; |
||
| 1036 | } |
||
| 1037 | $cookieHeader = 'Cookie:' . $version . substr($cookieHeader, 0, -1) . "\r\n"; |
||
| 1038 | 323 | } |
|
| 1039 | |||
| 1040 | 323 | $extraHeaders = ''; |
|
| 1041 | 161 | if (is_array($this->extra_headers) && $this->extra_headers) { |
|
| 1042 | $extraHeaders = implode("\r\n", $this->extra_headers) . "\r\n"; |
||
| 1043 | } |
||
| 1044 | 323 | ||
| 1045 | 64 | // omit port if default |
|
| 1046 | /// @todo add handling of http2, h2c in case they start being supported by fosckopen |
||
| 1047 | if (($port == 80 && in_array($method, array('http', 'http10', 'http11'))) || ($port == 443 && $method == 'https')) { |
||
| 1048 | $port = ''; |
||
| 1049 | } else { |
||
| 1050 | 323 | $port = ':' . $port; |
|
| 1051 | } |
||
| 1052 | 323 | ||
| 1053 | $op = 'POST ' . $uri . " HTTP/1.0\r\n" . |
||
| 1054 | 323 | 'User-Agent: ' . $opts['user_agent'] . "\r\n" . |
|
| 1055 | 272 | 'Host: ' . $server . $port . "\r\n" . |
|
| 1056 | $credentials . |
||
| 1057 | $proxyCredentials . |
||
| 1058 | 323 | $acceptedEncoding . |
|
| 1059 | 323 | $encodingHdr . |
|
| 1060 | 32 | 'Accept-Charset: ' . implode(',', $opts['accepted_charset_encodings']) . "\r\n" . |
|
| 1061 | 32 | $cookieHeader . |
|
| 1062 | 291 | 'Content-Type: ' . $req->getContentType() . "\r\n" . |
|
| 1063 | $extraHeaders . |
||
| 1064 | 'Content-Length: ' . strlen($payload) . "\r\n\r\n" . |
||
| 1065 | 291 | $payload; |
|
| 1066 | 32 | ||
| 1067 | 32 | if ($opts['debug'] > 1) { |
|
| 1068 | 259 | $this->getLogger()->debug("---SENDING---\n$op\n---END---"); |
|
| 1069 | 32 | } |
|
| 1070 | 32 | ||
| 1071 | $contextOptions = array(); |
||
| 1072 | if ($method == 'https') { |
||
| 1073 | 323 | if ($opts['cert'] != '') { |
|
| 1074 | 32 | $contextOptions['ssl']['local_cert'] = $opts['cert']; |
|
| 1075 | 32 | if ($opts['certpass'] != '') { |
|
| 1076 | 32 | $contextOptions['ssl']['passphrase'] = $opts['certpass']; |
|
| 1077 | } |
||
| 1078 | } |
||
| 1079 | if ($opts['cacert'] != '') { |
||
| 1080 | $contextOptions['ssl']['cafile'] = $opts['cacert']; |
||
| 1081 | } |
||
| 1082 | 323 | if ($opts['cacertdir'] != '') { |
|
| 1083 | $contextOptions['ssl']['capath'] = $opts['cacertdir']; |
||
| 1084 | 96 | } |
|
| 1085 | if ($opts['key'] != '') { |
||
| 1086 | $contextOptions['ssl']['local_pk'] = $opts['key']; |
||
| 1087 | } |
||
| 1088 | 96 | $contextOptions['ssl']['verify_peer'] = $opts['verifypeer']; |
|
| 1089 | $contextOptions['ssl']['verify_peer_name'] = $opts['verifypeer']; |
||
| 1090 | |||
| 1091 | if ($opts['sslversion'] != 0) { |
||
| 1092 | 96 | /// @see https://www.php.net/manual/en/curl.constants.php, |
|
| 1093 | /// https://www.php.net/manual/en/function.stream-socket-enable-crypto.php |
||
| 1094 | 96 | /// https://www.php.net/manual/en/migration56.openssl.php, |
|
| 1095 | /// https://wiki.php.net/rfc/improved-tls-constants |
||
| 1096 | switch($opts['sslversion']) { |
||
| 1097 | 96 | case 1: // TLSv1x |
|
| 1098 | if (version_compare(PHP_VERSION, '7.2.0', '>=')) { |
||
| 1099 | $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLS_CLIENT; |
||
| 1100 | } else { |
||
| 1101 | 96 | return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'], |
|
| 1102 | PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': TLS-any only is supported with PHP 7.2 or later'); |
||
| 1103 | } |
||
| 1104 | break; |
||
| 1105 | 96 | case 2: // SSLv2 |
|
| 1106 | $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_SSLv2_CLIENT; |
||
| 1107 | break; |
||
| 1108 | case 3: // SSLv3 |
||
| 1109 | $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_SSLv3_CLIENT; |
||
| 1110 | 96 | break; |
|
| 1111 | case 4: // TLSv1.0 - not always available? |
||
| 1112 | 96 | $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT; |
|
| 1113 | break; |
||
| 1114 | case 5: // TLSv1.1 - not always available? |
||
| 1115 | $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT; |
||
| 1116 | 323 | break; |
|
| 1117 | 64 | case 6: // TLSv1.2 - not always available? |
|
| 1118 | $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; |
||
| 1119 | break; |
||
| 1120 | 64 | case 7: // TLSv1.3 - not always available |
|
| 1121 | 64 | if (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT')) { |
|
| 1122 | $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT; |
||
| 1123 | } else { |
||
| 1124 | return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'], |
||
| 1125 | PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': TLS-1.3 only is supported with PHP 7.4 or later'); |
||
| 1126 | } |
||
| 1127 | break; |
||
| 1128 | default: |
||
| 1129 | return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'], |
||
| 1130 | PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': Unsupported required TLS version'); |
||
| 1131 | } |
||
| 1132 | } |
||
| 1133 | 323 | } |
|
| 1134 | 271 | ||
| 1135 | 271 | foreach ($opts['extrasockopts'] as $proto => $protoOpts) { |
|
| 1136 | 271 | foreach ($protoOpts as $key => $val) { |
|
| 1137 | $contextOptions[$proto][$key] = $val; |
||
| 1138 | 271 | } |
|
| 1139 | } |
||
| 1140 | |||
| 1141 | 323 | $context = stream_context_create($contextOptions); |
|
| 1142 | |||
| 1143 | if ($opts['timeout'] <= 0) { |
||
| 1144 | $connectTimeout = ini_get('default_socket_timeout'); |
||
| 1145 | 323 | } else { |
|
| 1146 | $connectTimeout = $opts['timeout']; |
||
| 1147 | } |
||
| 1148 | |||
| 1149 | 323 | $this->errno = 0; |
|
| 1150 | $this->errstr = ''; |
||
| 1151 | |||
| 1152 | /// @todo using `error_get_last` does not give us very detailed messages for connections errors, eg. for ssl |
||
| 1153 | /// problems on php 5.6 we get 'Connect error: stream_socket_client(): unable to connect to tls://localhost:443 (Unknown error) (0)', |
||
| 1154 | /// versus the more detailed warnings 'PHP Warning: stream_socket_client(): SSL operation failed with code 1. OpenSSL Error messages: |
||
| 1155 | /// error:0A0C0103:SSL routines::internal error in /home/docker/workspace/src/Client.php on line 1121 |
||
| 1156 | /// PHP Warning: stream_socket_client(): Failed to enable crypto in /home/docker/workspace/src/Client.php on line 1121 |
||
| 1157 | /// PHP Warning: stream_socket_client(): unable to connect to tls://localhost:443 (Unknown error) in /home/docker/workspace/src/Client.php on line 1121' |
||
| 1158 | /// This could be obviated by removing the `@` and capturing warnings via ob_start and co |
||
| 1159 | $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr, $connectTimeout, |
||
| 1160 | STREAM_CLIENT_CONNECT, $context); |
||
| 1161 | if ($fp) { |
||
| 1162 | if ($opts['timeout'] > 0) { |
||
| 1163 | stream_set_timeout($fp, $opts['timeout'], 0); |
||
| 1164 | } |
||
| 1165 | } else { |
||
| 1166 | if ($this->errstr == '') { |
||
| 1167 | $err = error_get_last(); |
||
| 1168 | $this->errstr = $err['message']; |
||
| 1169 | } |
||
| 1170 | |||
| 1171 | $this->errstr = 'Connect error: ' . $this->errstr; |
||
| 1172 | return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr . ' (' . $this->errno . ')'); |
||
| 1173 | } |
||
| 1174 | 66 | ||
| 1175 | /// @todo from here onwards, we can inject the results of stream_get_meta_data in the response. We could |
||
| 1176 | 66 | /// do that f.e. only in new debug level 3, or starting at v1 |
|
| 1177 | |||
| 1178 | if (!fputs($fp, $op, strlen($op))) { |
||
| 1179 | 66 | fclose($fp); |
|
| 1180 | 45 | $this->errstr = 'Write error'; |
|
| 1181 | 45 | return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr); |
|
| 1182 | } |
||
| 1183 | 45 | ||
| 1184 | $info = stream_get_meta_data($fp); |
||
| 1185 | if ($info['timed_out']) { |
||
| 1186 | fclose($fp); |
||
| 1187 | $this->errstr = 'Write timeout'; |
||
| 1188 | return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr); |
||
| 1189 | } |
||
| 1190 | |||
| 1191 | // Close socket before parsing. |
||
| 1192 | // It should yield slightly better execution times, and make easier recursive calls (e.g. to follow http redirects) |
||
| 1193 | $ipd = ''; |
||
| 1194 | do { |
||
| 1195 | // shall we check for $data === FALSE? |
||
| 1196 | // as per the manual, it signals an error |
||
| 1197 | $ipd .= fread($fp, 32768); |
||
| 1198 | |||
| 1199 | $info = stream_get_meta_data($fp); |
||
| 1200 | if ($info['timed_out']) { |
||
| 1201 | 23 | fclose($fp); |
|
| 1202 | $this->errstr = 'Read timeout'; |
||
| 1203 | return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr); |
||
| 1204 | 23 | } |
|
| 1205 | 23 | ||
| 1206 | } while (!feof($fp)); |
||
| 1207 | fclose($fp); |
||
| 1208 | |||
| 1209 | 23 | return $req->parseResponse($ipd, false, $opts['return_type']); |
|
| 1210 | 23 | } |
|
| 1211 | |||
| 1212 | /** |
||
| 1213 | * Contributed by Justin Miller |
||
| 1214 | * Requires curl to be built into PHP |
||
| 1215 | * NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers! |
||
| 1216 | * |
||
| 1217 | * @param Request $req |
||
| 1218 | * @param string $method |
||
| 1219 | * @param string $server |
||
| 1220 | * @param int $port |
||
| 1221 | 23 | * @param string $path |
|
| 1222 | * @param array $opts the keys/values match self::getOptions |
||
| 1223 | * @return Response |
||
| 1224 | * |
||
| 1225 | * @todo the $path arg atm is ignored. What to do if it is != $this->path? |
||
| 1226 | */ |
||
| 1227 | protected function sendViaCURL($req, $method, $server, $port, $path, $opts) |
||
| 1228 | { |
||
| 1229 | if (!function_exists('curl_init')) { |
||
| 1230 | $this->errstr = 'CURL unavailable on this install'; |
||
| 1231 | return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_curl'], PhpXmlRpc::$xmlrpcstr['no_curl']); |
||
| 1232 | } |
||
| 1233 | if ($method == 'https' || $method == 'h2') { |
||
| 1234 | // q: what about installs where we get back a string, but curl is linked to other ssl libs than openssl? |
||
| 1235 | 45 | if (($info = curl_version()) && |
|
| 1236 | ((is_string($info) && strpos($info, 'OpenSSL') === null) || (is_array($info) && !isset($info['ssl_version']))) |
||
| 1237 | ) { |
||
| 1238 | 45 | $this->errstr = 'SSL unavailable on this install'; |
|
| 1239 | 45 | return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_ssl'], PhpXmlRpc::$xmlrpcstr['no_ssl']); |
|
| 1240 | 45 | } |
|
| 1241 | 45 | } |
|
| 1242 | 45 | if (($method == 'h2' && !defined('CURL_HTTP_VERSION_2_0')) || |
|
| 1243 | 45 | ($method == 'h2c' && !defined('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE'))) { |
|
| 1244 | 45 | $this->errstr = 'HTTP/2 unavailable on this install'; |
|
| 1245 | return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_http2'], PhpXmlRpc::$xmlrpcstr['no_http2']); |
||
| 1246 | 45 | } |
|
| 1247 | 45 | ||
| 1248 | // BC - we go through prepareCurlHandle in case some subclass reimplemented it |
||
| 1249 | 45 | $curl = $this->prepareCurlHandle($req, $server, $port, $opts['timeout'], $opts['username'], $opts['password'], |
|
| 1250 | 45 | $opts['authtype'], $opts['cert'], $opts['certpass'], $opts['cacert'], $opts['cacertdir'], $opts['proxy'], |
|
| 1251 | $opts['proxyport'], $opts['proxy_user'], $opts['proxy_pass'], $opts['proxy_authtype'], $method, |
||
| 1252 | $opts['keepalive'], $opts['key'], $opts['keypass'], $opts['sslversion']); |
||
| 1253 | 45 | ||
| 1254 | if (!$curl) { |
||
| 1255 | 45 | return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] . |
|
| 1256 | ': error during curl initialization. Check php error log for details'); |
||
| 1257 | } |
||
| 1258 | |||
| 1259 | $result = curl_exec($curl); |
||
| 1260 | |||
| 1261 | 45 | if ($opts['debug'] > 1) { |
|
| 1262 | $message = "---CURL INFO---\n"; |
||
| 1263 | 45 | foreach (curl_getinfo($curl) as $name => $val) { |
|
| 1264 | if (is_array($val)) { |
||
| 1265 | 45 | $val = implode("\n", $val); |
|
| 1266 | } |
||
| 1267 | 22 | $message .= $name . ': ' . $val . "\n"; |
|
| 1268 | 22 | } |
|
| 1269 | $message .= '---END---'; |
||
| 1270 | $this->getLogger()->debug($message); |
||
| 1271 | 22 | } |
|
| 1272 | 22 | ||
| 1273 | if (!$result) { |
||
| 1274 | /// @todo we should use a better check here - what if we get back '' or '0'? |
||
| 1275 | |||
| 1276 | 22 | $this->errstr = 'no response'; |
|
| 1277 | 22 | $resp = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] . |
|
| 1278 | 22 | ': ' . curl_error($curl)); |
|
| 1279 | 22 | if (PHP_MAJOR_VERSION < 8) curl_close($curl); |
|
| 1280 | if ($opts['keepalive']) { |
||
| 1281 | $this->xmlrpc_curl_handle = null; |
||
| 1282 | 22 | } |
|
| 1283 | 22 | } else { |
|
| 1284 | 22 | if (!$opts['keepalive']) { |
|
| 1285 | if (PHP_MAJOR_VERSION < 8) curl_close($curl); |
||
| 1286 | } |
||
| 1287 | $resp = $req->parseResponse($result, true, $opts['return_type']); |
||
| 1288 | 22 | if ($opts['keepalive']) { |
|
| 1289 | 22 | /// @todo if we got back a 302 or 308, we should not reuse the curl handle for later calls |
|
| 1290 | 22 | if (is_object($resp) && $resp->faultCode() == PhpXmlRpc::$xmlrpcerr['http_error']) { |
|
| 1291 | if (PHP_MAJOR_VERSION < 8) curl_close($curl); |
||
| 1292 | 22 | $this->xmlrpc_curl_handle = null; |
|
| 1293 | 22 | } |
|
| 1294 | } |
||
| 1295 | } |
||
| 1296 | 22 | ||
| 1297 | 22 | return $resp; |
|
| 1298 | } |
||
| 1299 | |||
| 1300 | 22 | /** |
|
| 1301 | 22 | * @param Request $req |
|
| 1302 | * @param string $method |
||
| 1303 | * @param string $server |
||
| 1304 | * @param int $port |
||
| 1305 | * @param string $path |
||
| 1306 | * @param array $opts the keys/values match self::getOptions |
||
| 1307 | 22 | * @return \CurlHandle|resource|false |
|
| 1308 | * |
||
| 1309 | * @todo allow this method to either throw or return a Response, so that we can pass back to caller more info on errors |
||
| 1310 | */ |
||
| 1311 | 24 | protected function createCURLHandle($req, $method, $server, $port, $path, $opts) |
|
| 1312 | 24 | { |
|
| 1313 | if ($port == 0) { |
||
| 1314 | if (in_array($method, array('http', 'http10', 'http11', 'h2c'))) { |
||
| 1315 | 24 | $port = 80; |
|
| 1316 | 24 | } else { |
|
| 1317 | $port = 443; |
||
| 1318 | } |
||
| 1319 | } |
||
| 1320 | 24 | ||
| 1321 | 24 | // Only create the payload if it was not created previously |
|
| 1322 | 24 | $payload = $req->getPayload(); |
|
| 1323 | 24 | if (empty($payload)) { |
|
| 1324 | 24 | $payload = $req->serialize($opts['request_charset_encoding']); |
|
| 1325 | } |
||
| 1326 | |||
| 1327 | // Deflate request body and set appropriate request headers |
||
| 1328 | 24 | $encodingHdr = ''; |
|
| 1329 | 24 | if (($opts['request_compression'] == 'gzip' || $opts['request_compression'] == 'deflate')) { |
|
| 1330 | 22 | if ($opts['request_compression'] == 'gzip' && function_exists('gzencode')) { |
|
| 1331 | 22 | $a = @gzencode($payload); |
|
| 1332 | if ($a) { |
||
| 1333 | 22 | $payload = $a; |
|
| 1334 | $encodingHdr = 'Content-Encoding: gzip'; |
||
| 1335 | } else { |
||
| 1336 | 22 | $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzencode failure in compressing request'); |
|
| 1337 | } |
||
| 1338 | 22 | } else if (function_exists('gzcompress')) { |
|
| 1339 | $a = @gzcompress($payload); |
||
| 1340 | if ($a) { |
||
| 1341 | 22 | $payload = $a; |
|
| 1342 | 22 | $encodingHdr = 'Content-Encoding: deflate'; |
|
| 1343 | } else { |
||
| 1344 | $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzcompress failure in compressing request'); |
||
| 1345 | } |
||
| 1346 | } else { |
||
| 1347 | $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported by this PHP install'); |
||
| 1348 | 24 | } |
|
| 1349 | } else { |
||
| 1350 | if ($opts['request_compression'] != '') { |
||
| 1351 | $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported'); |
||
| 1352 | } |
||
| 1353 | } |
||
| 1354 | |||
| 1355 | if (!$opts['keepalive'] || !$this->xmlrpc_curl_handle) { |
||
| 1356 | if ($method == 'http11' || $method == 'http10' || $method == 'h2c') { |
||
| 1357 | $protocol = 'http'; |
||
| 1358 | } else { |
||
| 1359 | if ($method == 'h2') { |
||
| 1360 | $protocol = 'https'; |
||
| 1361 | } else { |
||
| 1362 | // http, https |
||
| 1363 | $protocol = $method; |
||
| 1364 | if (strpos($protocol, ':') !== false) { |
||
| 1365 | $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ": warning - attempted hacking attempt?. The curl protocol requested for the call is: '$protocol'"); |
||
| 1366 | return false; |
||
| 1367 | } |
||
| 1368 | } |
||
| 1369 | } |
||
| 1370 | $curl = curl_init($protocol . '://' . $server . ':' . $port . $path); |
||
| 1371 | if (!$curl) { |
||
| 1372 | return false; |
||
| 1373 | } |
||
| 1374 | if ($opts['keepalive']) { |
||
| 1375 | $this->xmlrpc_curl_handle = $curl; |
||
| 1376 | } |
||
| 1377 | } else { |
||
| 1378 | $curl = $this->xmlrpc_curl_handle; |
||
| 1379 | } |
||
| 1380 | |||
| 1381 | // results into variable |
||
| 1382 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); |
||
| 1383 | |||
| 1384 | if ($opts['debug'] > 1) { |
||
| 1385 | curl_setopt($curl, CURLOPT_VERBOSE, true); |
||
| 1386 | /// @todo redirect curlopt_stderr to some stream which can be piped to the logger |
||
| 1387 | } |
||
| 1388 | curl_setopt($curl, CURLOPT_USERAGENT, $opts['user_agent']); |
||
| 1389 | // required for XMLRPC: post the data |
||
| 1390 | curl_setopt($curl, CURLOPT_POST, 1); |
||
| 1391 | // the data |
||
| 1392 | curl_setopt($curl, CURLOPT_POSTFIELDS, $payload); |
||
| 1393 | |||
| 1394 | // return the header too |
||
| 1395 | curl_setopt($curl, CURLOPT_HEADER, 1); |
||
| 1396 | |||
| 1397 | // NB: if we set an empty string, CURL will add http header indicating |
||
| 1398 | // ALL methods it is supporting. This is possibly a better option than letting the user tell what curl can / cannot do... |
||
| 1399 | if (is_array($opts['accepted_compression']) && count($opts['accepted_compression'])) { |
||
| 1400 | //curl_setopt($curl, CURLOPT_ENCODING, implode(',', $opts['accepted_compression'])); |
||
| 1401 | // empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?) |
||
| 1402 | if (count($opts['accepted_compression']) == 1) { |
||
| 1403 | curl_setopt($curl, CURLOPT_ENCODING, $opts['accepted_compression'][0]); |
||
| 1404 | } else { |
||
| 1405 | curl_setopt($curl, CURLOPT_ENCODING, ''); |
||
| 1406 | } |
||
| 1407 | } |
||
| 1408 | // extra headers |
||
| 1409 | $headers = array('Content-Type: ' . $req->getContentType(), 'Accept-Charset: ' . implode(',', $opts['accepted_charset_encodings'])); |
||
| 1410 | // if no keepalive is wanted, let the server know it in advance |
||
| 1411 | if (!$opts['keepalive']) { |
||
| 1412 | $headers[] = 'Connection: close'; |
||
| 1413 | } |
||
| 1414 | // request compression header |
||
| 1415 | if ($encodingHdr) { |
||
| 1416 | $headers[] = $encodingHdr; |
||
| 1417 | } |
||
| 1418 | |||
| 1419 | if (is_array($this->extra_headers) && $this->extra_headers) { |
||
| 1420 | $headers = array_merge($headers, $this->extra_headers); |
||
| 1421 | } |
||
| 1422 | |||
| 1423 | // Fix the HTTP/1.1 417 Expectation Failed Bug (curl by default adds a 'Expect: 100-continue' header when POST |
||
| 1424 | // size exceeds 1025 bytes, apparently) |
||
| 1425 | $headers[] = 'Expect:'; |
||
| 1426 | |||
| 1427 | curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); |
||
| 1428 | // previous note: "timeout is borked" (on some old php/curl versions? It seems to work on 8.1. Maybe the issue |
||
| 1429 | // has to do with dns resolution...) |
||
| 1430 | if ($opts['timeout']) { |
||
| 1431 | curl_setopt($curl, CURLOPT_TIMEOUT, $opts['timeout']); |
||
| 1432 | } |
||
| 1433 | |||
| 1434 | // nb: for 'http' and 'https' we leave it up to curl to decide |
||
| 1435 | /// @see https://www.php.net/manual/en/curl.constants.php#constant.curl-http-version-1-0 |
||
| 1436 | /// @see https://curl.se/libcurl/c/CURLOPT_HTTP_VERSION.html |
||
| 1437 | /// @todo add support for CURL_VERSION_HTTP3 |
||
| 1438 | switch ($method) { |
||
| 1439 | case 'http10': |
||
| 1440 | curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); |
||
| 1441 | break; |
||
| 1442 | case 'http11': |
||
| 1443 | curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); |
||
| 1444 | break; |
||
| 1445 | case 'h2c': |
||
| 1446 | if (defined('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE')) { |
||
| 1447 | curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE); |
||
| 1448 | } else { |
||
| 1449 | $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. HTTP2 is not supported by the current PHP/curl install'); |
||
| 1450 | if (PHP_MAJOR_VERSION < 8) curl_close($curl); |
||
| 1451 | return false; |
||
| 1452 | } |
||
| 1453 | break; |
||
| 1454 | case 'h2': |
||
| 1455 | curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); |
||
| 1456 | break; |
||
| 1457 | } |
||
| 1458 | |||
| 1459 | if ($opts['username'] && $opts['password']) { |
||
| 1460 | curl_setopt($curl, CURLOPT_USERPWD, $opts['username'] . ':' . $opts['password']); |
||
| 1461 | if (defined('CURLOPT_HTTPAUTH')) { |
||
| 1462 | curl_setopt($curl, CURLOPT_HTTPAUTH, $opts['authtype']); |
||
| 1463 | } elseif ($opts['authtype'] != 1) { |
||
| 1464 | $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported by the current PHP/curl install'); |
||
| 1465 | if (PHP_MAJOR_VERSION < 8) curl_close($curl); |
||
| 1466 | return false; |
||
| 1467 | } |
||
| 1468 | } |
||
| 1469 | |||
| 1470 | // note: h2c is http2 without the https. No need to have it in this IF |
||
| 1471 | if ($method == 'https' || $method == 'h2') { |
||
| 1472 | // set cert file |
||
| 1473 | if ($opts['cert']) { |
||
| 1474 | curl_setopt($curl, CURLOPT_SSLCERT, $opts['cert']); |
||
| 1475 | } |
||
| 1476 | // set cert password |
||
| 1477 | if ($opts['certpass']) { |
||
| 1478 | curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $opts['certpass']); |
||
| 1479 | } |
||
| 1480 | // whether to verify remote host's cert |
||
| 1481 | curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $opts['verifypeer']); |
||
| 1482 | // set ca certificates file/dir |
||
| 1483 | if ($opts['cacert']) { |
||
| 1484 | curl_setopt($curl, CURLOPT_CAINFO, $opts['cacert']); |
||
| 1485 | } |
||
| 1486 | if ($opts['cacertdir']) { |
||
| 1487 | curl_setopt($curl, CURLOPT_CAPATH, $opts['cacertdir']); |
||
| 1488 | } |
||
| 1489 | // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?) |
||
| 1490 | if ($opts['key']) { |
||
| 1491 | curl_setopt($curl, CURLOPT_SSLKEY, $opts['key']); |
||
| 1492 | } |
||
| 1493 | // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?) |
||
| 1494 | if ($opts['keypass']) { |
||
| 1495 | curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $opts['keypass']); |
||
| 1496 | } |
||
| 1497 | // whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that |
||
| 1498 | // it matches the hostname used |
||
| 1499 | curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $opts['verifyhost']); |
||
| 1500 | // allow usage of different SSL versions |
||
| 1501 | curl_setopt($curl, CURLOPT_SSLVERSION, $opts['sslversion']); |
||
| 1502 | } |
||
| 1503 | |||
| 1504 | // proxy info |
||
| 1505 | if ($opts['proxy']) { |
||
| 1506 | if ($opts['proxyport'] == 0) { |
||
| 1507 | $opts['proxyport'] = 8080; // NB: even for HTTPS, local connection is on port 8080 |
||
| 1508 | } |
||
| 1509 | curl_setopt($curl, CURLOPT_PROXY, $opts['proxy'] . ':' . $opts['proxyport']); |
||
| 1510 | if ($opts['proxy_user']) { |
||
| 1511 | curl_setopt($curl, CURLOPT_PROXYUSERPWD, $opts['proxy_user'] . ':' . $opts['proxy_pass']); |
||
| 1512 | if (defined('CURLOPT_PROXYAUTH')) { |
||
| 1513 | curl_setopt($curl, CURLOPT_PROXYAUTH, $opts['proxy_authtype']); |
||
| 1514 | } elseif ($opts['proxy_authtype'] != 1) { |
||
| 1515 | $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported by the current PHP/curl install'); |
||
| 1516 | if (PHP_MAJOR_VERSION < 8) curl_close($curl); |
||
| 1517 | return false; |
||
| 1518 | } |
||
| 1519 | } |
||
| 1520 | } |
||
| 1521 | |||
| 1522 | // NB: should we build cookie http headers by hand rather than let CURL do it? |
||
| 1523 | // NB: the following code does not honour 'expires', 'path' and 'domain' cookie attributes set to client obj by the user... |
||
| 1524 | if (count($opts['cookies'])) { |
||
| 1525 | $cookieHeader = ''; |
||
| 1526 | foreach ($opts['cookies'] as $name => $cookie) { |
||
| 1527 | $cookieHeader .= $name . '=' . $cookie['value'] . '; '; |
||
| 1528 | } |
||
| 1529 | curl_setopt($curl, CURLOPT_COOKIE, substr($cookieHeader, 0, -2)); |
||
| 1530 | } |
||
| 1531 | |||
| 1532 | foreach ($opts['extracurlopts'] as $opt => $val) { |
||
| 1533 | curl_setopt($curl, $opt, $val); |
||
| 1534 | } |
||
| 1535 | |||
| 1536 | if ($opts['debug'] > 1) { |
||
| 1537 | $this->getLogger()->debug("---SENDING---\n$payload\n---END---"); |
||
| 1538 | } |
||
| 1539 | |||
| 1540 | return $curl; |
||
| 1541 | } |
||
| 1542 | |||
| 1543 | /** |
||
| 1544 | * Send an array of requests and return an array of responses. |
||
| 1545 | * |
||
| 1546 | * Unless $this->no_multicall has been set to true, it will try first to use one single xml-rpc call to server method |
||
| 1547 | * system.multicall, and revert to sending many successive calls in case of failure. |
||
| 1548 | * This failure is also stored in $this->no_multicall for subsequent calls. |
||
| 1549 | * Unfortunately, there is no server error code universally used to denote the fact that multicall is unsupported, |
||
| 1550 | * so there is no way to reliably distinguish between that and a temporary failure. |
||
| 1551 | * If you are sure that server supports multicall and do not want to fallback to using many single calls, set the |
||
| 1552 | * 2np parameter to FALSE. |
||
| 1553 | * |
||
| 1554 | * NB: trying to shoehorn extra functionality into existing syntax has resulted |
||
| 1555 | * in pretty much convoluted code... |
||
| 1556 | * |
||
| 1557 | * @param Request[] $reqs an array of Request objects |
||
| 1558 | * @param bool $noFallback When true, upon receiving an error during multicall, multiple single calls will not be |
||
| 1559 | * attempted. |
||
| 1560 | * Deprecated alternative, was: int - "connection timeout (in seconds). See the details in the |
||
| 1561 | * docs for the send() method". Please use setOption instead to set a timeout |
||
| 1562 | * @param string $method deprecated. Was: "the http protocol variant to be used. See the details in the docs for the send() method." |
||
| 1563 | * Please use the constructor to set an http protocol variant. |
||
| 1564 | * @param boolean $fallback deprecated. Was: "when true, upon receiving an error during multicall, multiple single |
||
| 1565 | * calls will be attempted" |
||
| 1566 | * @return Response[] |
||
| 1567 | */ |
||
| 1568 | public function multicall($reqs, $timeout = 0, $method = '', $fallback = true) |
||
| 1569 | { |
||
| 1570 | // BC |
||
| 1571 | if (is_bool($timeout) && $fallback === true) { |
||
| 1572 | $fallback = !$timeout; |
||
| 1573 | $timeout = 0; |
||
| 1574 | } |
||
| 1575 | |||
| 1576 | if ($method == '') { |
||
| 1577 | $method = $this->method; |
||
| 1578 | } |
||
| 1579 | |||
| 1580 | if (!$this->no_multicall) { |
||
| 1581 | $results = $this->_try_multicall($reqs, $timeout, $method); |
||
| 1582 | /// @todo how to handle the case of $this->return_type = xml? |
||
| 1583 | if (is_array($results)) { |
||
| 1584 | // System.multicall succeeded |
||
| 1585 | return $results; |
||
| 1586 | } else { |
||
| 1587 | // either system.multicall is unsupported by server, or the call failed for some other reason. |
||
| 1588 | // Feature creep: is there a way to tell apart unsupported multicall from other faults? |
||
| 1589 | if ($fallback) { |
||
| 1590 | // Don't try it next time... |
||
| 1591 | $this->no_multicall = true; |
||
| 1592 | } else { |
||
| 1593 | $result = $results; |
||
| 1594 | } |
||
| 1595 | } |
||
| 1596 | } else { |
||
| 1597 | // override fallback, in case careless user tries to do two |
||
| 1598 | // opposite things at the same time |
||
| 1599 | $fallback = true; |
||
| 1600 | } |
||
| 1601 | |||
| 1602 | $results = array(); |
||
| 1603 | if ($fallback) { |
||
| 1604 | // system.multicall is (probably) unsupported by server: emulate multicall via multiple requests |
||
| 1605 | /// @todo use curl multi_ functions to make this quicker (see the implementation in the parallel.php demo) |
||
| 1606 | foreach ($reqs as $req) { |
||
| 1607 | $results[] = $this->send($req, $timeout, $method); |
||
| 1608 | } |
||
| 1609 | } else { |
||
| 1610 | // user does NOT want to fallback on many single calls: since we should always return an array of responses, |
||
| 1611 | // we return an array with the same error repeated n times |
||
| 1612 | foreach ($reqs as $req) { |
||
| 1613 | $results[] = $result; |
||
|
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
Loading history...
|
|||
| 1614 | } |
||
| 1615 | } |
||
| 1616 | |||
| 1617 | return $results; |
||
| 1618 | } |
||
| 1619 | |||
| 1620 | /** |
||
| 1621 | * Attempt to boxcar $reqs via system.multicall. |
||
| 1622 | * |
||
| 1623 | * @param Request[] $reqs |
||
| 1624 | * @param int $timeout |
||
| 1625 | * @param string $method |
||
| 1626 | * @return Response[]|Response a single Response when the call returned a fault / does not conform to what we expect |
||
| 1627 | * from a multicall response |
||
| 1628 | */ |
||
| 1629 | protected function _try_multicall($reqs, $timeout, $method) |
||
| 1630 | { |
||
| 1631 | // Construct multicall request |
||
| 1632 | $calls = array(); |
||
| 1633 | foreach ($reqs as $req) { |
||
| 1634 | $call['methodName'] = new Value($req->method(), 'string'); |
||
| 1635 | $numParams = $req->getNumParams(); |
||
| 1636 | $params = array(); |
||
| 1637 | for ($i = 0; $i < $numParams; $i++) { |
||
| 1638 | $params[$i] = $req->getParam($i); |
||
| 1639 | } |
||
| 1640 | $call['params'] = new Value($params, 'array'); |
||
| 1641 | $calls[] = new Value($call, 'struct'); |
||
| 1642 | } |
||
| 1643 | $multiCall = new static::$requestClass('system.multicall'); |
||
| 1644 | $multiCall->addParam(new Value($calls, 'array')); |
||
| 1645 | |||
| 1646 | // Attempt RPC call |
||
| 1647 | $result = $this->send($multiCall, $timeout, $method); |
||
| 1648 | |||
| 1649 | if ($result->faultCode() != 0) { |
||
| 1650 | // call to system.multicall failed |
||
| 1651 | return $result; |
||
| 1652 | } |
||
| 1653 | |||
| 1654 | // Unpack responses. |
||
| 1655 | $rets = $result->value(); |
||
| 1656 | $response = array(); |
||
| 1657 | |||
| 1658 | if ($this->return_type == 'xml') { |
||
| 1659 | for ($i = 0; $i < count($reqs); $i++) { |
||
| 1660 | /// @todo can we do better? we set the complete xml into each response... |
||
| 1661 | $response[] = new static::$responseClass($rets, 0, '', 'xml', $result->httpResponse()); |
||
| 1662 | } |
||
| 1663 | |||
| 1664 | } elseif ($this->return_type == 'phpvals') { |
||
| 1665 | if (!is_array($rets)) { |
||
| 1666 | // bad return type from system.multicall |
||
| 1667 | return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'], |
||
| 1668 | PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': not an array', 'phpvals', $result->httpResponse()); |
||
| 1669 | } |
||
| 1670 | $numRets = count($rets); |
||
| 1671 | if ($numRets != count($reqs)) { |
||
| 1672 | // wrong number of return values. |
||
| 1673 | return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'], |
||
| 1674 | PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': incorrect number of responses', 'phpvals', |
||
| 1675 | $result->httpResponse()); |
||
| 1676 | } |
||
| 1677 | |||
| 1678 | for ($i = 0; $i < $numRets; $i++) { |
||
| 1679 | $val = $rets[$i]; |
||
| 1680 | if (!is_array($val)) { |
||
| 1681 | return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'], |
||
| 1682 | PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array or struct", |
||
| 1683 | 'phpvals', $result->httpResponse()); |
||
| 1684 | } |
||
| 1685 | switch (count($val)) { |
||
| 1686 | case 1: |
||
| 1687 | if (!isset($val[0])) { |
||
| 1688 | // Bad value |
||
| 1689 | return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'], |
||
| 1690 | PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has no value", |
||
| 1691 | 'phpvals', $result->httpResponse()); |
||
| 1692 | } |
||
| 1693 | // Normal return value |
||
| 1694 | $response[$i] = new static::$responseClass($val[0], 0, '', 'phpvals', $result->httpResponse()); |
||
| 1695 | break; |
||
| 1696 | case 2: |
||
| 1697 | /// @todo remove usage of @: it is apparently quite slow |
||
| 1698 | $code = @$val['faultCode']; |
||
| 1699 | if (!is_int($code)) { |
||
| 1700 | /// @todo should we check that it is != 0? |
||
| 1701 | return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'], |
||
| 1702 | PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode", |
||
| 1703 | 'phpvals', $result->httpResponse()); |
||
| 1704 | } |
||
| 1705 | $str = @$val['faultString']; |
||
| 1706 | if (!is_string($str)) { |
||
| 1707 | return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'], |
||
| 1708 | PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no FaultString", |
||
| 1709 | 'phpvals', $result->httpResponse()); |
||
| 1710 | } |
||
| 1711 | $response[$i] = new static::$responseClass(0, $code, $str, 'phpvals', $result->httpResponse()); |
||
| 1712 | break; |
||
| 1713 | default: |
||
| 1714 | return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'], |
||
| 1715 | PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items", |
||
| 1716 | 'phpvals', $result->httpResponse()); |
||
| 1717 | } |
||
| 1718 | } |
||
| 1719 | |||
| 1720 | } else { |
||
| 1721 | // return type == 'xmlrpcvals' |
||
| 1722 | if ($rets->kindOf() != 'array') { |
||
| 1723 | return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'], |
||
| 1724 | PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array", 'xmlrpcvals', |
||
|
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
| 1725 | $result->httpResponse()); |
||
| 1726 | } |
||
| 1727 | $numRets = $rets->count(); |
||
| 1728 | if ($numRets != count($reqs)) { |
||
| 1729 | // wrong number of return values. |
||
| 1730 | return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'], |
||
| 1731 | PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': incorrect number of responses', 'xmlrpcvals', |
||
| 1732 | $result->httpResponse()); |
||
| 1733 | } |
||
| 1734 | |||
| 1735 | foreach ($rets as $i => $val) { |
||
| 1736 | switch ($val->kindOf()) { |
||
| 1737 | case 'array': |
||
| 1738 | if ($val->count() != 1) { |
||
| 1739 | return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'], |
||
| 1740 | PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items", |
||
| 1741 | 'phpvals', $result->httpResponse()); |
||
| 1742 | } |
||
| 1743 | // Normal return value |
||
| 1744 | $response[] = new static::$responseClass($val[0], 0, '', 'xmlrpcvals', $result->httpResponse()); |
||
| 1745 | break; |
||
| 1746 | case 'struct': |
||
| 1747 | if ($val->count() != 2) { |
||
| 1748 | return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'], |
||
| 1749 | PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items", |
||
| 1750 | 'phpvals', $result->httpResponse()); |
||
| 1751 | } |
||
| 1752 | /** @var Value $code */ |
||
| 1753 | $code = @$val['faultCode']; |
||
| 1754 | if (!$code || $code->kindOf() != 'scalar' || $code->scalarTyp() != 'int') { |
||
| 1755 | return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'], |
||
| 1756 | PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode", |
||
| 1757 | 'xmlrpcvals', $result->httpResponse()); |
||
| 1758 | } |
||
| 1759 | /** @var Value $str */ |
||
| 1760 | $str = @$val['faultString']; |
||
| 1761 | if (!$str || $str->kindOf() != 'scalar' || $str->scalarTyp() != 'string') { |
||
| 1762 | return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'], |
||
| 1763 | PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultString", |
||
| 1764 | 'xmlrpcvals', $result->httpResponse()); |
||
| 1765 | } |
||
| 1766 | $response[] = new static::$responseClass(0, $code->scalarVal(), $str->scalarVal(), 'xmlrpcvals', $result->httpResponse()); |
||
| 1767 | break; |
||
| 1768 | default: |
||
| 1769 | return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'], |
||
| 1770 | PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array or struct", |
||
| 1771 | 'xmlrpcvals', $result->httpResponse()); |
||
| 1772 | } |
||
| 1773 | } |
||
| 1774 | } |
||
| 1775 | |||
| 1776 | return $response; |
||
| 1777 | } |
||
| 1778 | |||
| 1779 | // *** BC layer *** |
||
| 1780 | |||
| 1781 | /** |
||
| 1782 | * NB: always goes via socket, never curl |
||
| 1783 | * |
||
| 1784 | * @deprecated |
||
| 1785 | * |
||
| 1786 | * @param Request $req |
||
| 1787 | * @param string $server |
||
| 1788 | * @param int $port |
||
| 1789 | * @param int $timeout |
||
| 1790 | * @param string $username |
||
| 1791 | * @param string $password |
||
| 1792 | * @param int $authType |
||
| 1793 | * @param string $proxyHost |
||
| 1794 | * @param int $proxyPort |
||
| 1795 | * @param string $proxyUsername |
||
| 1796 | * @param string $proxyPassword |
||
| 1797 | * @param int $proxyAuthType |
||
| 1798 | * @param string $method |
||
| 1799 | * @return Response |
||
| 1800 | */ |
||
| 1801 | protected function sendPayloadHTTP10($req, $server, $port, $timeout = 0, $username = '', $password = '', |
||
| 1802 | $authType = 1, $proxyHost = '', $proxyPort = 0, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, |
||
| 1803 | $method = 'http') |
||
| 1804 | { |
||
| 1805 | $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated'); |
||
| 1806 | |||
| 1807 | return $this->sendPayloadSocket($req, $server, $port, $timeout, $username, $password, $authType, null, null, |
||
| 1808 | null, null, $proxyHost, $proxyPort, $proxyUsername, $proxyPassword, $proxyAuthType, $method); |
||
| 1809 | } |
||
| 1810 | |||
| 1811 | /** |
||
| 1812 | * NB: always goes via curl, never socket |
||
| 1813 | * |
||
| 1814 | * @deprecated |
||
| 1815 | * |
||
| 1816 | * @param Request $req |
||
| 1817 | * @param string $server |
||
| 1818 | * @param int $port |
||
| 1819 | * @param int $timeout |
||
| 1820 | * @param string $username |
||
| 1821 | * @param string $password |
||
| 1822 | * @param int $authType |
||
| 1823 | * @param string $cert |
||
| 1824 | * @param string $certPass |
||
| 1825 | * @param string $caCert |
||
| 1826 | * @param string $caCertDir |
||
| 1827 | * @param string $proxyHost |
||
| 1828 | * @param int $proxyPort |
||
| 1829 | * @param string $proxyUsername |
||
| 1830 | * @param string $proxyPassword |
||
| 1831 | * @param int $proxyAuthType |
||
| 1832 | * @param bool $keepAlive |
||
| 1833 | * @param string $key |
||
| 1834 | * @param string $keyPass |
||
| 1835 | * @param int $sslVersion |
||
| 1836 | * @return Response |
||
| 1837 | */ |
||
| 1838 | protected function sendPayloadHTTPS($req, $server, $port, $timeout = 0, $username = '', $password = '', |
||
| 1839 | $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0, |
||
| 1840 | $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $keepAlive = false, $key = '', $keyPass = '', |
||
| 1841 | $sslVersion = 0) |
||
| 1842 | { |
||
| 1843 | $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated'); |
||
| 1844 | |||
| 1845 | return $this->sendPayloadCURL($req, $server, $port, $timeout, $username, |
||
| 1846 | $password, $authType, $cert, $certPass, $caCert, $caCertDir, $proxyHost, $proxyPort, |
||
| 1847 | $proxyUsername, $proxyPassword, $proxyAuthType, 'https', $keepAlive, $key, $keyPass, $sslVersion); |
||
| 1848 | } |
||
| 1849 | |||
| 1850 | /** |
||
| 1851 | * @deprecated |
||
| 1852 | * |
||
| 1853 | * @param Request $req |
||
| 1854 | * @param string $server |
||
| 1855 | * @param int $port |
||
| 1856 | * @param int $timeout |
||
| 1857 | * @param string $username |
||
| 1858 | * @param string $password |
||
| 1859 | * @param int $authType only value supported is 1 |
||
| 1860 | * @param string $cert |
||
| 1861 | * @param string $certPass |
||
| 1862 | * @param string $caCert |
||
| 1863 | * @param string $caCertDir |
||
| 1864 | * @param string $proxyHost |
||
| 1865 | * @param int $proxyPort |
||
| 1866 | * @param string $proxyUsername |
||
| 1867 | * @param string $proxyPassword |
||
| 1868 | * @param int $proxyAuthType only value supported is 1 |
||
| 1869 | * @param string $method 'http' (synonym for 'http10'), 'http10' or 'https' |
||
| 1870 | * @param string $key |
||
| 1871 | * @param string $keyPass @todo not implemented yet. |
||
| 1872 | * @param int $sslVersion |
||
| 1873 | * @return Response |
||
| 1874 | */ |
||
| 1875 | protected function sendPayloadSocket($req, $server, $port, $timeout = 0, $username = '', $password = '', |
||
| 1876 | $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0, |
||
| 1877 | $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'http', $key = '', $keyPass = '', |
||
| 1878 | $sslVersion = 0) |
||
| 1879 | { |
||
| 1880 | $this->logDeprecationUnlessCalledBy('send'); |
||
| 1881 | |||
| 1882 | return $this->sendViaSocket($req, $method, $server, $port, $this->path, array( |
||
| 1883 | 'accepted_charset_encodings' => $this->accepted_charset_encodings, |
||
| 1884 | 'accepted_compression' => $this->accepted_compression, |
||
| 1885 | 'authtype' => $authType, |
||
| 1886 | 'cacert' => $caCert, |
||
| 1887 | 'cacertdir' => $caCertDir, |
||
| 1888 | 'cert' => $cert, |
||
| 1889 | 'certpass' => $certPass, |
||
| 1890 | 'cookies' => $this->cookies, |
||
| 1891 | 'debug' => $this->debug, |
||
| 1892 | 'extracurlopts' => $this->extracurlopts, |
||
| 1893 | 'extrasockopts' => $this->extrasockopts, |
||
| 1894 | 'keepalive' => $this->keepalive, |
||
| 1895 | 'key' => $key, |
||
| 1896 | 'keypass' => $keyPass, |
||
| 1897 | 'no_multicall' => $this->no_multicall, |
||
| 1898 | 'password' => $password, |
||
| 1899 | 'proxy' => $proxyHost, |
||
| 1900 | 'proxy_authtype' => $proxyAuthType, |
||
| 1901 | 'proxy_pass' => $proxyPassword, |
||
| 1902 | 'proxyport' => $proxyPort, |
||
| 1903 | 'proxy_user' => $proxyUsername, |
||
| 1904 | 'request_charset_encoding' => $this->request_charset_encoding, |
||
| 1905 | 'request_compression' => $this->request_compression, |
||
| 1906 | 'return_type' => $this->return_type, |
||
| 1907 | 'sslversion' => $sslVersion, |
||
| 1908 | 'timeout' => $timeout, |
||
| 1909 | 'username' => $username, |
||
| 1910 | 'user_agent' => $this->user_agent, |
||
| 1911 | 'use_curl' => $this->use_curl, |
||
| 1912 | 'verifyhost' => $this->verifyhost, |
||
| 1913 | 'verifypeer' => $this->verifypeer, |
||
| 1914 | )); |
||
| 1915 | } |
||
| 1916 | |||
| 1917 | /** |
||
| 1918 | * @deprecated |
||
| 1919 | * |
||
| 1920 | * @param Request $req |
||
| 1921 | * @param string $server |
||
| 1922 | * @param int $port |
||
| 1923 | * @param int $timeout |
||
| 1924 | * @param string $username |
||
| 1925 | * @param string $password |
||
| 1926 | * @param int $authType |
||
| 1927 | * @param string $cert |
||
| 1928 | * @param string $certPass |
||
| 1929 | * @param string $caCert |
||
| 1930 | * @param string $caCertDir |
||
| 1931 | * @param string $proxyHost |
||
| 1932 | * @param int $proxyPort |
||
| 1933 | * @param string $proxyUsername |
||
| 1934 | * @param string $proxyPassword |
||
| 1935 | * @param int $proxyAuthType |
||
| 1936 | * @param string $method 'http' (let curl decide), 'http10', 'http11', 'https', 'h2c' or 'h2' |
||
| 1937 | * @param bool $keepAlive |
||
| 1938 | * @param string $key |
||
| 1939 | * @param string $keyPass |
||
| 1940 | * @param int $sslVersion |
||
| 1941 | * @return Response |
||
| 1942 | */ |
||
| 1943 | protected function sendPayloadCURL($req, $server, $port, $timeout = 0, $username = '', $password = '', |
||
| 1944 | $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0, |
||
| 1945 | $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'https', $keepAlive = false, $key = '', |
||
| 1946 | $keyPass = '', $sslVersion = 0) |
||
| 1947 | { |
||
| 1948 | $this->logDeprecationUnlessCalledBy('send'); |
||
| 1949 | |||
| 1950 | return $this->sendViaCURL($req, $method, $server, $port, $this->path, array( |
||
| 1951 | 'accepted_charset_encodings' => $this->accepted_charset_encodings, |
||
| 1952 | 'accepted_compression' => $this->accepted_compression, |
||
| 1953 | 'authtype' => $authType, |
||
| 1954 | 'cacert' => $caCert, |
||
| 1955 | 'cacertdir' => $caCertDir, |
||
| 1956 | 'cert' => $cert, |
||
| 1957 | 'certpass' => $certPass, |
||
| 1958 | 'cookies' => $this->cookies, |
||
| 1959 | 'debug' => $this->debug, |
||
| 1960 | 'extracurlopts' => $this->extracurlopts, |
||
| 1961 | 'extrasockopts' => $this->extrasockopts, |
||
| 1962 | 'keepalive' => $keepAlive, |
||
| 1963 | 'key' => $key, |
||
| 1964 | 'keypass' => $keyPass, |
||
| 1965 | 'no_multicall' => $this->no_multicall, |
||
| 1966 | 'password' => $password, |
||
| 1967 | 'proxy' => $proxyHost, |
||
| 1968 | 'proxy_authtype' => $proxyAuthType, |
||
| 1969 | 'proxy_pass' => $proxyPassword, |
||
| 1970 | 'proxyport' => $proxyPort, |
||
| 1971 | 'proxy_user' => $proxyUsername, |
||
| 1972 | 'request_charset_encoding' => $this->request_charset_encoding, |
||
| 1973 | 'request_compression' => $this->request_compression, |
||
| 1974 | 'return_type' => $this->return_type, |
||
| 1975 | 'sslversion' => $sslVersion, |
||
| 1976 | 'timeout' => $timeout, |
||
| 1977 | 'username' => $username, |
||
| 1978 | 'user_agent' => $this->user_agent, |
||
| 1979 | 'use_curl' => $this->use_curl, |
||
| 1980 | 'verifyhost' => $this->verifyhost, |
||
| 1981 | 'verifypeer' => $this->verifypeer, |
||
| 1982 | )); |
||
| 1983 | } |
||
| 1984 | |||
| 1985 | /** |
||
| 1986 | * @deprecated |
||
| 1987 | * |
||
| 1988 | * @param $req |
||
| 1989 | * @param $server |
||
| 1990 | * @param $port |
||
| 1991 | * @param $timeout |
||
| 1992 | * @param $username |
||
| 1993 | * @param $password |
||
| 1994 | * @param $authType |
||
| 1995 | * @param $cert |
||
| 1996 | * @param $certPass |
||
| 1997 | * @param $caCert |
||
| 1998 | * @param $caCertDir |
||
| 1999 | * @param $proxyHost |
||
| 2000 | * @param $proxyPort |
||
| 2001 | * @param $proxyUsername |
||
| 2002 | * @param $proxyPassword |
||
| 2003 | * @param $proxyAuthType |
||
| 2004 | * @param $method |
||
| 2005 | * @param $keepAlive |
||
| 2006 | * @param $key |
||
| 2007 | * @param $keyPass |
||
| 2008 | * @param $sslVersion |
||
| 2009 | * @return false|\CurlHandle|resource |
||
| 2010 | */ |
||
| 2011 | protected function prepareCurlHandle($req, $server, $port, $timeout = 0, $username = '', $password = '', |
||
| 2012 | $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0, |
||
| 2013 | $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'https', $keepAlive = false, $key = '', |
||
| 2014 | $keyPass = '', $sslVersion = 0) |
||
| 2015 | { |
||
| 2016 | $this->logDeprecationUnlessCalledBy('sendViaCURL'); |
||
| 2017 | |||
| 2018 | return $this->createCURLHandle($req, $method, $server, $port, $this->path, array( |
||
| 2019 | 'accepted_charset_encodings' => $this->accepted_charset_encodings, |
||
| 2020 | 'accepted_compression' => $this->accepted_compression, |
||
| 2021 | 'authtype' => $authType, |
||
| 2022 | 'cacert' => $caCert, |
||
| 2023 | 'cacertdir' => $caCertDir, |
||
| 2024 | 'cert' => $cert, |
||
| 2025 | 'certpass' => $certPass, |
||
| 2026 | 'cookies' => $this->cookies, |
||
| 2027 | 'debug' => $this->debug, |
||
| 2028 | 'extracurlopts' => $this->extracurlopts, |
||
| 2029 | 'keepalive' => $keepAlive, |
||
| 2030 | 'key' => $key, |
||
| 2031 | 'keypass' => $keyPass, |
||
| 2032 | 'no_multicall' => $this->no_multicall, |
||
| 2033 | 'password' => $password, |
||
| 2034 | 'proxy' => $proxyHost, |
||
| 2035 | 'proxy_authtype' => $proxyAuthType, |
||
| 2036 | 'proxy_pass' => $proxyPassword, |
||
| 2037 | 'proxyport' => $proxyPort, |
||
| 2038 | 'proxy_user' => $proxyUsername, |
||
| 2039 | 'request_charset_encoding' => $this->request_charset_encoding, |
||
| 2040 | 'request_compression' => $this->request_compression, |
||
| 2041 | 'return_type' => $this->return_type, |
||
| 2042 | 'sslversion' => $sslVersion, |
||
| 2043 | 'timeout' => $timeout, |
||
| 2044 | 'username' => $username, |
||
| 2045 | 'user_agent' => $this->user_agent, |
||
| 2046 | 'use_curl' => $this->use_curl, |
||
| 2047 | 'verifyhost' => $this->verifyhost, |
||
| 2048 | 'verifypeer' => $this->verifypeer, |
||
| 2049 | )); |
||
| 2050 | } |
||
| 2051 | |||
| 2052 | // we have to make this return by ref in order to allow calls such as `$resp->_cookies['name'] = ['value' => 'something'];` |
||
| 2053 | public function &__get($name) |
||
| 2054 | { |
||
| 2055 | if (in_array($name, static::$options)) { |
||
| 2056 | $this->logDeprecation('Getting property Client::' . $name . ' is deprecated'); |
||
| 2057 | return $this->$name; |
||
| 2058 | } |
||
| 2059 | |||
| 2060 | switch ($name) { |
||
| 2061 | case 'errno': |
||
| 2062 | case 'errstr': |
||
| 2063 | case 'method': |
||
| 2064 | case 'server': |
||
| 2065 | case 'port': |
||
| 2066 | case 'path': |
||
| 2067 | $this->logDeprecation('Getting property Client::' . $name . ' is deprecated'); |
||
| 2068 | return $this->$name; |
||
| 2069 | default: |
||
| 2070 | /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout... |
||
| 2071 | $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1); |
||
| 2072 | trigger_error('Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING); |
||
| 2073 | $result = null; |
||
| 2074 | return $result; |
||
| 2075 | } |
||
| 2076 | } |
||
| 2077 | |||
| 2078 | public function __set($name, $value) |
||
| 2079 | { |
||
| 2080 | if (in_array($name, static::$options)) { |
||
| 2081 | $this->logDeprecation('Setting property Client::' . $name . ' is deprecated'); |
||
| 2082 | $this->$name = $value; |
||
| 2083 | return; |
||
| 2084 | } |
||
| 2085 | |||
| 2086 | switch ($name) { |
||
| 2087 | case 'errno': |
||
| 2088 | case 'errstr': |
||
| 2089 | case 'method': |
||
| 2090 | case 'server': |
||
| 2091 | case 'port': |
||
| 2092 | case 'path': |
||
| 2093 | $this->logDeprecation('Setting property Client::' . $name . ' is deprecated'); |
||
| 2094 | $this->$name = $value; |
||
| 2095 | return; |
||
| 2096 | default: |
||
| 2097 | /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout... |
||
| 2098 | $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1); |
||
| 2099 | trigger_error('Undefined property via __set(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING); |
||
| 2100 | } |
||
| 2101 | } |
||
| 2102 | |||
| 2103 | public function __isset($name) |
||
| 2104 | { |
||
| 2105 | if (in_array($name, static::$options)) { |
||
| 2106 | $this->logDeprecation('Checking property Client::' . $name . ' is deprecated'); |
||
| 2107 | return isset($this->$name); |
||
| 2108 | } |
||
| 2109 | |||
| 2110 | switch ($name) { |
||
| 2111 | case 'errno': |
||
| 2112 | case 'errstr': |
||
| 2113 | case 'method': |
||
| 2114 | case 'server': |
||
| 2115 | case 'port': |
||
| 2116 | case 'path': |
||
| 2117 | $this->logDeprecation('Checking property Client::' . $name . ' is deprecated'); |
||
| 2118 | return isset($this->$name); |
||
| 2119 | default: |
||
| 2120 | return false; |
||
| 2121 | } |
||
| 2122 | } |
||
| 2123 | |||
| 2124 | public function __unset($name) |
||
| 2125 | { |
||
| 2126 | if (in_array($name, static::$options)) { |
||
| 2127 | $this->logDeprecation('Unsetting property Client::' . $name . ' is deprecated'); |
||
| 2128 | unset($this->$name); |
||
| 2129 | return; |
||
| 2130 | } |
||
| 2131 | |||
| 2132 | switch ($name) { |
||
| 2133 | case 'errno': |
||
| 2134 | case 'errstr': |
||
| 2135 | case 'method': |
||
| 2136 | case 'server': |
||
| 2137 | case 'port': |
||
| 2138 | case 'path': |
||
| 2139 | $this->logDeprecation('Unsetting property Client::' . $name . ' is deprecated'); |
||
| 2140 | unset($this->$name); |
||
| 2141 | return; |
||
| 2142 | default: |
||
| 2143 | /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout... |
||
| 2144 | $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1); |
||
| 2145 | trigger_error('Undefined property via __unset(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING); |
||
| 2146 | } |
||
| 2147 | } |
||
| 2148 | } |
||
| 2149 |