Client::prepareCurlHandle()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 38
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 32
c 2
b 0
f 0
nc 1
nop 21
dl 0
loc 38
ccs 0
cts 0
cp 0
crap 2
rs 9.408

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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;
0 ignored issues
show
Documentation Bug introduced by
The property $proxyport was declared of type integer, but $proxyPort is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
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) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $compMethod of type string to the boolean false. If you are specifically checking for an empty string, consider using the more explicit === '' instead.
Loading history...
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $port of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
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) || 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', '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
     * @return Response|Response[] Note that the client will always return a Response object, even if the call fails
818 374
     *
819 367
     * @todo allow throwing exceptions instead of returning responses in case of failed calls and/or Fault responses
820
     * @todo refactor: we now support many options besides connection timeout and http version to use. Why only privilege those?
821
     */
822 1
    public function send($req, $timeout = 0, $method = '')
823
    {
824
        if ($method !== '' || $timeout !== 0) {
825
            $this->logDeprecation("Using non-default values for arguments 'method' and 'timeout' when calling method " . __METHOD__ . ' is deprecated');
826
        }
827 1
828 1
        // if user does not specify http protocol, use native method of this client
829
        // (i.e. method set during call to constructor)
830 1
        if ($method == '') {
831
            $method = $this->method;
832
        }
833 374
834
        if ($timeout == 0) {
835
            $timeout = $this->timeout;
836
        }
837
838
        if (is_array($req)) {
839
            // $req is an array of Requests
840
            /// @todo switch to the new syntax for multicall
841
            return $this->multicall($req, $timeout, $method);
842
        } elseif (is_string($req)) {
843 374
            $n = new static::$requestClass('');
844
            /// @todo we should somehow allow the caller to declare a custom contenttype too, esp. for the charset declaration
845
            $n->setPayload($req);
846
            $req = $n;
847 374
        }
848 374
849 374
        // where req is a Request
850
        $req->setDebug($this->debug);
851 374
852
        /// @todo we could be smarter about this:
853 374
        ///       - not force usage of curl if it is not present
854
        ///       - not force usage of curl for https (minor BC)
855
        ///       - use the presence of curl_extra_opts or socket_extra_opts as a hint
856
        $useCurl = ($this->use_curl == self::USE_CURL_ALWAYS) || ($this->use_curl == self::USE_CURL_AUTO && (
857
            in_array($method, array('https', 'http11', 'h2c', 'h2')) ||
858
            ($this->username != '' && $this->authtype != 1) ||
859
            ($this->proxy != '' && $this->proxy_user != '' && $this->proxy_authtype != 1)
860
            // uncomment the following if not forcing curl always for 'https'
861
            //|| ($this->sslversion == 7 && PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION == '7.3')
862
            //|| ($this->sslversion != 0 && PHP_MAJOR_VERSION < 6)
863
        ));
864
865
        // BC - we go through sendPayloadCURL/sendPayloadSocket in case some subclass reimplemented those
866
        if ($useCurl) {
867
            $r = $this->sendPayloadCURL(
0 ignored issues
show
Deprecated Code introduced by
The function PhpXmlRpc\Client::sendPayloadCURL() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

867
            $r = /** @scrutinizer ignore-deprecated */ $this->sendPayloadCURL(
Loading history...
868
                $req,
869
                $this->server,
870
                $this->port,
871
                $timeout,
872
                $this->username,
873
                $this->password,
874
                $this->authtype,
875
                $this->cert,
876
                $this->certpass,
877
                $this->cacert,
878
                $this->cacertdir,
879
                $this->proxy,
880
                $this->proxyport,
881
                $this->proxy_user,
882
                $this->proxy_pass,
883
                $this->proxy_authtype,
884
                // BC
885
                $method == 'http11' ? 'http' : $method,
886 322
                $this->keepalive,
887
                $this->key,
888
                $this->keypass,
889
                $this->sslversion
890
            );
891 322
        } else {
892
            $r = $this->sendPayloadSocket(
0 ignored issues
show
Deprecated Code introduced by
The function PhpXmlRpc\Client::sendPayloadSocket() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

892
            $r = /** @scrutinizer ignore-deprecated */ $this->sendPayloadSocket(
Loading history...
893
                $req,
894
                $this->server,
895 322
                $this->port,
896
                $timeout,
897 96
                $this->username,
898 96
                $this->password,
899
                $this->authtype,
900
                $this->cert,
901
                $this->certpass,
902
                $this->cacert,
903
                $this->cacertdir,
904 322
                $this->proxy,
905 322
                $this->proxyport,
906
                $this->proxy_user,
907
                $this->proxy_pass,
908
                $this->proxy_authtype,
909
                $method,
910 322
                $this->key,
911
                $this->keypass,
912
                $this->sslversion
913
            );
914
        }
915 322
916
        return $r;
917 322
    }
918
919
    /**
920
     * @param Request $req
921
     * @param string $method
922
     * @param string $server
923
     * @param int $port
924
     * @param string $path
925
     * @param array $opts
926
     * @return Response
927
     */
928
    protected function sendViaSocket($req, $method, $server, $port, $path, $opts)
929 322
    {
930
        /// @todo log a warning if passed an unsupported method
931
932 1
        // Only create the payload if it was not created previously
933 1
        /// @todo what if the request's payload was created with a different encoding?
934 1
        ///       Also, if we do not call serialize(), the request will not set its content-type to have the charset declared
935 1
        $payload = $req->getPayload();
936 1
        if (empty($payload)) {
937
            $payload = $req->serialize($opts['request_charset_encoding']);
938
        }
939 322
940 160
        // Deflate request body and set appropriate request headers
941
        $encodingHdr = '';
942 322
        if ($opts['request_compression'] == 'gzip' || $opts['request_compression'] == 'deflate') {
943
            if ($opts['request_compression'] == 'gzip' && function_exists('gzencode')) {
944 322
                $a = @gzencode($payload);
945
                if ($a) {
946
                    $payload = $a;
947
                    $encodingHdr = "Content-Encoding: gzip\r\n";
948
                } else {
949
                    $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzencode failure in compressing request');
950 322
                }
951
            } else if (function_exists('gzcompress')) {
952
                $a = @gzcompress($payload);
953 323
                if ($a) {
954
                    $payload = $a;
955
                    $encodingHdr = "Content-Encoding: deflate\r\n";
956
                } else {
957
                    $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzcompress failure in compressing request');
958 323
                }
959 322
            } else {
960 226
                $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported by this PHP install');
961
            }
962 96
        } else {
963
            if ($opts['request_compression'] != '') {
964
                $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported');
965
            }
966
        }
967 323
968 302
        // thanks to Grant Rauscher
969
        $credentials = '';
970
        if ($opts['username'] != '') {
971
            if ($opts['authtype'] != 1) {
972 323
                $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported with HTTP 1.0');
973 323
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
974 64
                    PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': only Basic auth is supported with HTTP 1.0');
975 32
            }
976 32
            $credentials = 'Authorization: Basic ' . base64_encode($opts['username'] . ':' . $opts['password']) . "\r\n";
977 32
        }
978 32
979
        $acceptedEncoding = '';
980
        if (is_array($opts['accepted_compression']) && count($opts['accepted_compression'])) {
981 32
            $acceptedEncoding = 'Accept-Encoding: ' . implode(', ', $opts['accepted_compression']) . "\r\n";
982 32
        }
983 32
984 64
        if ($port == 0) {
985
            $port = ($method === 'https') ? 443 : 80;
986
        }
987
988 259
        $proxyCredentials = '';
989
        if ($opts['proxy']) {
990
            if ($opts['proxyport'] == 0) {
991 323
                $opts['proxyport'] = 8080;
992 323
            }
993 64
            $connectServer = $opts['proxy'];
994
            $connectPort = $opts['proxyport'];
995 259
            $transport = 'tcp';
996 32
            $protocol = $method;
997
            if ($method === 'http10' || $method === 'http11') {
998
                $protocol = 'http';
999 227
            } elseif ($method === 'h2') {
1000
                $protocol = 'https';
1001
            } else if (strpos($protocol, ':') !== false) {
1002 323
                $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ": warning - attempted hacking attempt?. The protocol requested for the call is: '$protocol'");
1003 323
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'], PhpXmlRpc::$xmlrpcerr['unsupported_option'] .
1004 322
                    " attempted hacking attempt?. The protocol requested for the call is: '$protocol'");
1005
            }
1006
            /// @todo this does not work atm (tested at least with an http proxy forwarding to an https server) - we
1007 56
            ///       should implement the CONNECT protocol
1008
            $uri = $protocol . '://' . $server . ':' . $port . $path;
1009
            if ($opts['proxy_user'] != '') {
1010
                if ($opts['proxy_authtype'] != 1) {
1011 323
                    $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported with HTTP 1.0');
1012
                    return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
1013 323
                        PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': only Basic auth to proxy is supported with socket transport');
1014
                }
1015
                $proxyCredentials = 'Proxy-Authorization: Basic ' . base64_encode($opts['proxy_user'] . ':' .
1016
                    $opts['proxy_pass']) . "\r\n";
1017 323
            }
1018
        } else {
1019 323
            $connectServer = $server;
1020
            $connectPort = $port;
1021 323
            /// @todo should we add support for 'h2' method? If so, is it 'tls' or 'tcp' ?
1022
            $transport = ($method === 'https') ? 'tls' : 'tcp';
1023
            $uri = $path;
1024 323
        }
1025
1026
        // Cookie generation, as per RFC 6265
1027
        // NB: the following code does not honour 'expires', 'path' and 'domain' cookie attributes set to client obj by the user...
1028 323
        $cookieHeader = '';
1029
        if (count($opts['cookies'])) {
1030
            $version = '';
1031 66
            foreach ($opts['cookies'] as $name => $cookie) {
1032 64
                /// @todo should we sanitize the cookie value on behalf of the user? See setCookie comments
1033
                $cookieHeader .= ' ' . $name . '=' . $cookie['value'] . ";";
1034 2
            }
1035
            $cookieHeader = 'Cookie:' . $version . substr($cookieHeader, 0, -1) . "\r\n";
1036
        }
1037
1038 323
        $extraHeaders = '';
1039
        if (is_array($this->extra_headers) && $this->extra_headers) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->extra_headers of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1040 323
            $extraHeaders = implode("\r\n", $this->extra_headers) . "\r\n";
1041 161
        }
1042
1043
        // omit port if default
1044 323
        /// @todo add handling of http2, h2c in case they start being supported by fosckopen
1045 64
        if (($port == 80 && in_array($method, array('http', 'http10', 'http11'))) || ($port == 443 && $method == 'https')) {
1046
            $port = '';
1047
        } else {
1048
            $port = ':' . $port;
1049
        }
1050 323
1051
        $op = 'POST ' . $uri . " HTTP/1.0\r\n" .
1052 323
            'User-Agent: ' . $opts['user_agent'] . "\r\n" .
1053
            'Host: ' . $server . $port . "\r\n" .
1054 323
            $credentials .
1055 272
            $proxyCredentials .
1056
            $acceptedEncoding .
1057
            $encodingHdr .
1058 323
            'Accept-Charset: ' . implode(',', $opts['accepted_charset_encodings']) . "\r\n" .
1059 323
            $cookieHeader .
1060 32
            'Content-Type: ' . $req->getContentType() . "\r\n" .
1061 32
            $extraHeaders .
1062 291
            'Content-Length: ' . strlen($payload) . "\r\n\r\n" .
1063
            $payload;
1064
1065 291
        if ($opts['debug'] > 1) {
1066 32
            $this->getLogger()->debug("---SENDING---\n$op\n---END---");
1067 32
        }
1068 259
1069 32
        $contextOptions = array();
1070 32
        if ($method == 'https') {
1071
            if ($opts['cert'] != '') {
1072
                $contextOptions['ssl']['local_cert'] = $opts['cert'];
1073 323
                if ($opts['certpass'] != '') {
1074 32
                    $contextOptions['ssl']['passphrase'] = $opts['certpass'];
1075 32
                }
1076 32
            }
1077
            if ($opts['cacert'] != '') {
1078
                $contextOptions['ssl']['cafile'] = $opts['cacert'];
1079
            }
1080
            if ($opts['cacertdir'] != '') {
1081
                $contextOptions['ssl']['capath'] = $opts['cacertdir'];
1082 323
            }
1083
            if ($opts['key'] != '') {
1084 96
                $contextOptions['ssl']['local_pk'] = $opts['key'];
1085
            }
1086
            $contextOptions['ssl']['verify_peer'] = $opts['verifypeer'];
1087
            $contextOptions['ssl']['verify_peer_name'] = $opts['verifypeer'];
1088 96
1089
            if ($opts['sslversion'] != 0) {
1090
                /// @see https://www.php.net/manual/en/curl.constants.php,
1091
                ///      https://www.php.net/manual/en/function.stream-socket-enable-crypto.php
1092 96
                ///      https://www.php.net/manual/en/migration56.openssl.php,
1093
                ///      https://wiki.php.net/rfc/improved-tls-constants
1094 96
                switch($opts['sslversion']) {
1095
                    case 1: // TLSv1x
1096
                        if (version_compare(PHP_VERSION, '7.2.0', '>=')) {
1097 96
                            $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLS_CLIENT;
1098
                        } else {
1099
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
1100
                                PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': TLS-any only is supported with PHP 7.2 or later');
1101 96
                        }
1102
                        break;
1103
                    case 2: // SSLv2
1104
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_SSLv2_CLIENT;
1105 96
                        break;
1106
                    case 3: // SSLv3
1107
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_SSLv3_CLIENT;
1108
                        break;
1109
                    case 4: // TLSv1.0 - not always available?
1110 96
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT;
1111
                        break;
1112 96
                    case 5: // TLSv1.1 - not always available?
1113
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
1114
                        break;
1115
                    case 6: // TLSv1.2 - not always available?
1116 323
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
1117 64
                        break;
1118
                    case 7: // TLSv1.3 - not always available
1119
                        if (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT')) {
1120 64
                            $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT;
1121 64
                        } else {
1122
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
1123
                                PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': TLS-1.3 only is supported with PHP 7.4 or later');
1124
                        }
1125
                        break;
1126
                    default:
1127
                        return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
1128
                            PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': Unsupported required TLS version');
1129
                }
1130
            }
1131
        }
1132
1133 323
        foreach ($opts['extrasockopts'] as $proto => $protoOpts) {
1134 271
            foreach ($protoOpts as $key => $val) {
1135 271
                $contextOptions[$proto][$key] = $val;
1136 271
            }
1137
        }
1138 271
1139
        $context = stream_context_create($contextOptions);
1140
1141 323
        if ($opts['timeout'] <= 0) {
1142
            $connectTimeout = ini_get('default_socket_timeout');
1143
        } else {
1144
            $connectTimeout = $opts['timeout'];
1145 323
        }
1146
1147
        $this->errno = 0;
1148
        $this->errstr = '';
1149 323
1150
        /// @todo using `error_get_last` does not give us very detailed messages for connections errors, eg. for ssl
1151
        ///       problems on php 5.6 we get 'Connect error: stream_socket_client(): unable to connect to tls://localhost:443 (Unknown error) (0)',
1152
        ///       versus the more detailed warnings 'PHP Warning:  stream_socket_client(): SSL operation failed with code 1. OpenSSL Error messages:
1153
        ///         error:0A0C0103:SSL routines::internal error in /home/docker/workspace/src/Client.php on line 1121
1154
        ///         PHP Warning:  stream_socket_client(): Failed to enable crypto in /home/docker/workspace/src/Client.php on line 1121
1155
        ///         PHP Warning:  stream_socket_client(): unable to connect to tls://localhost:443 (Unknown error) in /home/docker/workspace/src/Client.php on line 1121'
1156
        ///       This could be obviated by removing the `@` and capturing warnings via ob_start and co
1157
        $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr, $connectTimeout,
1158
            STREAM_CLIENT_CONNECT, $context);
1159
        if ($fp) {
0 ignored issues
show
introduced by
$fp is of type false|resource, thus it always evaluated to false.
Loading history...
1160
            if ($opts['timeout'] > 0) {
1161
                stream_set_timeout($fp, $opts['timeout'], 0);
1162
            }
1163
        } else {
1164
            if ($this->errstr == '') {
1165
                $err = error_get_last();
1166
                $this->errstr = $err['message'];
1167
            }
1168
1169
            $this->errstr = 'Connect error: ' . $this->errstr;
1170
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr . ' (' . $this->errno . ')');
1171
        }
1172
1173
        /// @todo from here onwards, we can inject the results of stream_get_meta_data in the response. We could
1174 66
        ///       do that f.e. only in new debug level 3, or starting at v1
1175
1176 66
        if (!fputs($fp, $op, strlen($op))) {
1177
            fclose($fp);
1178
            $this->errstr = 'Write error';
1179 66
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr);
1180 45
        }
1181 45
1182
        $info = stream_get_meta_data($fp);
1183 45
        if ($info['timed_out']) {
1184
            fclose($fp);
1185
            $this->errstr = 'Write timeout';
1186
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr);
1187
        }
1188
1189
        // Close socket before parsing.
1190
        // It should yield slightly better execution times, and make easier recursive calls (e.g. to follow http redirects)
1191
        $ipd = '';
1192
        do {
1193
            // shall we check for $data === FALSE?
1194
            // as per the manual, it signals an error
1195
            $ipd .= fread($fp, 32768);
1196
1197
            $info = stream_get_meta_data($fp);
1198
            if ($info['timed_out']) {
1199
                fclose($fp);
1200
                $this->errstr = 'Read timeout';
1201 23
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr);
1202
            }
1203
1204 23
        } while (!feof($fp));
1205 23
        fclose($fp);
1206
1207
        return $req->parseResponse($ipd, false, $opts['return_type']);
1208
    }
1209 23
1210 23
    /**
1211
     * Contributed by Justin Miller
1212
     * Requires curl to be built into PHP
1213
     * NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers!
1214
     *
1215
     * @param Request $req
1216
     * @param string $method
1217
     * @param string $server
1218
     * @param int $port
1219
     * @param string $path
1220
     * @param array $opts the keys/values match self::getOptions
1221 23
     * @return Response
1222
     *
1223
     * @todo the $path arg atm is ignored. What to do if it is != $this->path?
1224
     */
1225
    protected function sendViaCURL($req, $method, $server, $port, $path, $opts)
0 ignored issues
show
Unused Code introduced by
The parameter $path is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1225
    protected function sendViaCURL($req, $method, $server, $port, /** @scrutinizer ignore-unused */ $path, $opts)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1226
    {
1227
        if (!function_exists('curl_init')) {
1228
            $this->errstr = 'CURL unavailable on this install';
1229
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_curl'], PhpXmlRpc::$xmlrpcstr['no_curl']);
1230
        }
1231
        if ($method == 'https' || $method == 'h2') {
1232
            // q: what about installs where we get back a string, but curl is linked to other ssl libs than openssl?
1233
            if (($info = curl_version()) &&
1234
                ((is_string($info) && strpos($info, 'OpenSSL') === null) || (is_array($info) && !isset($info['ssl_version'])))
0 ignored issues
show
introduced by
The condition is_string($info) is always false.
Loading history...
1235 45
            ) {
1236
                $this->errstr = 'SSL unavailable on this install';
1237
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_ssl'], PhpXmlRpc::$xmlrpcstr['no_ssl']);
1238 45
            }
1239 45
        }
1240 45
        if (($method == 'h2' && !defined('CURL_HTTP_VERSION_2_0')) ||
1241 45
            ($method == 'h2c' && !defined('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE'))) {
1242 45
            $this->errstr = 'HTTP/2 unavailable on this install';
1243 45
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_http2'], PhpXmlRpc::$xmlrpcstr['no_http2']);
1244 45
        }
1245
1246 45
        // BC - we go through prepareCurlHandle in case some subclass reimplemented it
1247 45
        $curl = $this->prepareCurlHandle($req, $server, $port, $opts['timeout'], $opts['username'], $opts['password'],
0 ignored issues
show
Deprecated Code introduced by
The function PhpXmlRpc\Client::prepareCurlHandle() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1247
        $curl = /** @scrutinizer ignore-deprecated */ $this->prepareCurlHandle($req, $server, $port, $opts['timeout'], $opts['username'], $opts['password'],
Loading history...
1248
            $opts['authtype'], $opts['cert'], $opts['certpass'], $opts['cacert'], $opts['cacertdir'], $opts['proxy'],
1249 45
            $opts['proxyport'], $opts['proxy_user'], $opts['proxy_pass'], $opts['proxy_authtype'], $method,
1250 45
            $opts['keepalive'], $opts['key'], $opts['keypass'], $opts['sslversion']);
1251
1252
        if (!$curl) {
1253 45
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] .
1254
                ': error during curl initialization. Check php error log for details');
1255 45
        }
1256
1257
        $result = curl_exec($curl);
1258
1259
        if ($opts['debug'] > 1) {
1260
            $message = "---CURL INFO---\n";
1261 45
            foreach (curl_getinfo($curl) as $name => $val) {
1262
                if (is_array($val)) {
1263 45
                    $val = implode("\n", $val);
1264
                }
1265 45
                $message .= $name . ': ' . $val . "\n";
1266
            }
1267 22
            $message .= '---END---';
1268 22
            $this->getLogger()->debug($message);
1269
        }
1270
1271 22
        if (!$result) {
1272 22
            /// @todo we should use a better check here - what if we get back '' or '0'?
1273
1274
            $this->errstr = 'no response';
1275
            $resp = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] .
1276 22
                ': ' . curl_error($curl));
1277 22
            curl_close($curl);
1278 22
            if ($opts['keepalive']) {
1279 22
                $this->xmlrpc_curl_handle = null;
1280
            }
1281
        } else {
1282 22
            if (!$opts['keepalive']) {
1283 22
                curl_close($curl);
1284 22
            }
1285
            $resp = $req->parseResponse($result, true, $opts['return_type']);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type true; however, parameter $data of PhpXmlRpc\Request::parseResponse() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1285
            $resp = $req->parseResponse(/** @scrutinizer ignore-type */ $result, true, $opts['return_type']);
Loading history...
1286
            if ($opts['keepalive']) {
1287
                /// @todo if we got back a 302 or 308, we should not reuse the curl handle for later calls
1288 22
                if ($resp->faultCode() == PhpXmlRpc::$xmlrpcerr['http_error']) {
1289 22
                    curl_close($curl);
1290 22
                    $this->xmlrpc_curl_handle = null;
1291
                }
1292 22
            }
1293 22
        }
1294
1295
        return $resp;
1296 22
    }
1297 22
1298
    /**
1299
     * @param Request $req
1300 22
     * @param string $method
1301 22
     * @param string $server
1302
     * @param int $port
1303
     * @param string $path
1304
     * @param array $opts the keys/values match self::getOptions
1305
     * @return \CurlHandle|resource|false
1306
     *
1307 22
     * @todo allow this method to either throw or return a Response, so that we can pass back to caller more info on errors
1308
     */
1309
    protected function createCURLHandle($req, $method, $server, $port, $path, $opts)
1310
    {
1311 24
        if ($port == 0) {
1312 24
            if (in_array($method, array('http', 'http10', 'http11', 'h2c'))) {
1313
                $port = 80;
1314
            } else {
1315 24
                $port = 443;
1316 24
            }
1317
        }
1318
1319
        // Only create the payload if it was not created previously
1320 24
        $payload = $req->getPayload();
1321 24
        if (empty($payload)) {
1322 24
            $payload = $req->serialize($opts['request_charset_encoding']);
1323 24
        }
1324 24
1325
        // Deflate request body and set appropriate request headers
1326
        $encodingHdr = '';
1327
        if (($opts['request_compression'] == 'gzip' || $opts['request_compression'] == 'deflate')) {
1328 24
            if ($opts['request_compression'] == 'gzip' && function_exists('gzencode')) {
1329 24
                $a = @gzencode($payload);
1330 22
                if ($a) {
1331 22
                    $payload = $a;
1332
                    $encodingHdr = 'Content-Encoding: gzip';
1333 22
                } else {
1334
                    $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzencode failure in compressing request');
1335
                }
1336 22
            } else if (function_exists('gzcompress')) {
1337
                $a = @gzcompress($payload);
1338 22
                if ($a) {
1339
                    $payload = $a;
1340
                    $encodingHdr = 'Content-Encoding: deflate';
1341 22
                } else {
1342 22
                    $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzcompress failure in compressing request');
1343
                }
1344
            } else {
1345
                $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported by this PHP install');
1346
            }
1347
        } else {
1348 24
            if ($opts['request_compression'] != '') {
1349
                $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported');
1350
            }
1351
        }
1352
1353
        if (!$opts['keepalive'] || !$this->xmlrpc_curl_handle) {
1354
            if ($method == 'http11' || $method == 'http10' || $method == 'h2c') {
1355
                $protocol = 'http';
1356
            } else {
1357
                if ($method == 'h2') {
1358
                    $protocol = 'https';
1359
                } else {
1360
                    // http, https
1361
                    $protocol = $method;
1362
                    if (strpos($protocol, ':') !== false) {
1363
                        $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ": warning - attempted hacking attempt?. The curl protocol requested for the call is: '$protocol'");
1364
                        return false;
1365
                    }
1366
                }
1367
            }
1368
            $curl = curl_init($protocol . '://' . $server . ':' . $port . $path);
1369
            if (!$curl) {
1370
                return false;
1371
            }
1372
            if ($opts['keepalive']) {
1373
                $this->xmlrpc_curl_handle = $curl;
1374
            }
1375
        } else {
1376
            $curl = $this->xmlrpc_curl_handle;
1377
        }
1378
1379
        // results into variable
1380
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
1381
1382
        if ($opts['debug'] > 1) {
1383
            curl_setopt($curl, CURLOPT_VERBOSE, true);
1384
            /// @todo redirect curlopt_stderr to some stream which can be piped to the logger
1385
        }
1386
        curl_setopt($curl, CURLOPT_USERAGENT, $opts['user_agent']);
1387
        // required for XMLRPC: post the data
1388
        curl_setopt($curl, CURLOPT_POST, 1);
1389
        // the data
1390
        curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
1391
1392
        // return the header too
1393
        curl_setopt($curl, CURLOPT_HEADER, 1);
1394
1395
        // NB: if we set an empty string, CURL will add http header indicating
1396
        // ALL methods it is supporting. This is possibly a better option than letting the user tell what curl can / cannot do...
1397
        if (is_array($opts['accepted_compression']) && count($opts['accepted_compression'])) {
1398
            //curl_setopt($curl, CURLOPT_ENCODING, implode(',', $opts['accepted_compression']));
1399
            // empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1400
            if (count($opts['accepted_compression']) == 1) {
1401
                curl_setopt($curl, CURLOPT_ENCODING, $opts['accepted_compression'][0]);
1402
            } else {
1403
                curl_setopt($curl, CURLOPT_ENCODING, '');
1404
            }
1405
        }
1406
        // extra headers
1407
        $headers = array('Content-Type: ' . $req->getContentType(), 'Accept-Charset: ' . implode(',', $opts['accepted_charset_encodings']));
1408
        // if no keepalive is wanted, let the server know it in advance
1409
        if (!$opts['keepalive']) {
1410
            $headers[] = 'Connection: close';
1411
        }
1412
        // request compression header
1413
        if ($encodingHdr) {
1414
            $headers[] = $encodingHdr;
1415
        }
1416
1417
        if (is_array($this->extra_headers) && $this->extra_headers) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->extra_headers of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1418
            $headers = array_merge($headers, $this->extra_headers);
1419
        }
1420
1421
        // Fix the HTTP/1.1 417 Expectation Failed Bug (curl by default adds a 'Expect: 100-continue' header when POST
1422
        // size exceeds 1025 bytes, apparently)
1423
        $headers[] = 'Expect:';
1424
1425
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
1426
        // previous note: "timeout is borked" (on some old php/curl versions? It seems to work on 8.1. Maybe the issue
1427
        // has to do with dns resolution...)
1428
        if ($opts['timeout']) {
1429
            curl_setopt($curl, CURLOPT_TIMEOUT, $opts['timeout']);
1430
        }
1431
1432
        // nb: for 'https' we leave it up to curl to decide
1433
        switch ($method) {
1434
            case 'http10':
1435
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
1436
                break;
1437
            case 'http11':
1438
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
1439
                break;
1440
            case 'h2c':
1441
                if (defined('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE')) {
1442
                    curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE);
1443
                } else {
1444
                    $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. HTTP2 is not supported by the current PHP/curl install');
1445
                    curl_close($curl);
1446
                    return false;
1447
                }
1448
                break;
1449
            case 'h2':
1450
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
1451
                break;
1452
        }
1453
1454
        if ($opts['username'] && $opts['password']) {
1455
            curl_setopt($curl, CURLOPT_USERPWD, $opts['username'] . ':' . $opts['password']);
1456
            if (defined('CURLOPT_HTTPAUTH')) {
1457
                curl_setopt($curl, CURLOPT_HTTPAUTH, $opts['authtype']);
1458
            } elseif ($opts['authtype'] != 1) {
1459
                $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported by the current PHP/curl install');
1460
                curl_close($curl);
1461
                return false;
1462
            }
1463
        }
1464
1465
        // note: h2c is http2 without the https. No need to have it in this IF
1466
        if ($method == 'https' || $method == 'h2') {
1467
            // set cert file
1468
            if ($opts['cert']) {
1469
                curl_setopt($curl, CURLOPT_SSLCERT, $opts['cert']);
1470
            }
1471
            // set cert password
1472
            if ($opts['certpass']) {
1473
                curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $opts['certpass']);
1474
            }
1475
            // whether to verify remote host's cert
1476
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $opts['verifypeer']);
1477
            // set ca certificates file/dir
1478
            if ($opts['cacert']) {
1479
                curl_setopt($curl, CURLOPT_CAINFO, $opts['cacert']);
1480
            }
1481
            if ($opts['cacertdir']) {
1482
                curl_setopt($curl, CURLOPT_CAPATH, $opts['cacertdir']);
1483
            }
1484
            // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1485
            if ($opts['key']) {
1486
                curl_setopt($curl, CURLOPT_SSLKEY, $opts['key']);
1487
            }
1488
            // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1489
            if ($opts['keypass']) {
1490
                curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $opts['keypass']);
1491
            }
1492
            // whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that
1493
            // it matches the hostname used
1494
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $opts['verifyhost']);
1495
            // allow usage of different SSL versions
1496
            curl_setopt($curl, CURLOPT_SSLVERSION, $opts['sslversion']);
1497
        }
1498
1499
        // proxy info
1500
        if ($opts['proxy']) {
1501
            if ($opts['proxyport'] == 0) {
1502
                $opts['proxyport'] = 8080; // NB: even for HTTPS, local connection is on port 8080
1503
            }
1504
            curl_setopt($curl, CURLOPT_PROXY, $opts['proxy'] . ':' . $opts['proxyport']);
1505
            if ($opts['proxy_user']) {
1506
                curl_setopt($curl, CURLOPT_PROXYUSERPWD, $opts['proxy_user'] . ':' . $opts['proxy_pass']);
1507
                if (defined('CURLOPT_PROXYAUTH')) {
1508
                    curl_setopt($curl, CURLOPT_PROXYAUTH, $opts['proxy_authtype']);
1509
                } elseif ($opts['proxy_authtype'] != 1) {
1510
                    $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported by the current PHP/curl install');
1511
                    curl_close($curl);
1512
                    return false;
1513
                }
1514
            }
1515
        }
1516
1517
        // NB: should we build cookie http headers by hand rather than let CURL do it?
1518
        // NB: the following code does not honour 'expires', 'path' and 'domain' cookie attributes set to client obj by the user...
1519
        if (count($opts['cookies'])) {
1520
            $cookieHeader = '';
1521
            foreach ($opts['cookies'] as $name => $cookie) {
1522
                $cookieHeader .= $name . '=' . $cookie['value'] . '; ';
1523
            }
1524
            curl_setopt($curl, CURLOPT_COOKIE, substr($cookieHeader, 0, -2));
1525
        }
1526
1527
        foreach ($opts['extracurlopts'] as $opt => $val) {
1528
            curl_setopt($curl, $opt, $val);
1529
        }
1530
1531
        if ($opts['debug'] > 1) {
1532
            $this->getLogger()->debug("---SENDING---\n$payload\n---END---");
1533
        }
1534
1535
        return $curl;
1536
    }
1537
1538
    /**
1539
     * Send an array of requests and return an array of responses.
1540
     *
1541
     * Unless $this->no_multicall has been set to true, it will try first to use one single xml-rpc call to server method
1542
     * system.multicall, and revert to sending many successive calls in case of failure.
1543
     * This failure is also stored in $this->no_multicall for subsequent calls.
1544
     * Unfortunately, there is no server error code universally used to denote the fact that multicall is unsupported,
1545
     * so there is no way to reliably distinguish between that and a temporary failure.
1546
     * If you are sure that server supports multicall and do not want to fallback to using many single calls, set the
1547
     * 2np parameter to FALSE.
1548
     *
1549
     * NB: trying to shoehorn extra functionality into existing syntax has resulted
1550
     * in pretty much convoluted code...
1551
     *
1552
     * @param Request[] $reqs an array of Request objects
1553
     * @param bool $noFallback When true, upon receiving an error during multicall, multiple single calls will not be
1554
     *                         attempted.
1555
     *                         Deprecated alternative, was: int - "connection timeout (in seconds). See the details in the
1556
     *                         docs for the send() method". Please use setOption instead to set a timeout
1557
     * @param string $method deprecated. Was: "the http protocol variant to be used. See the details in the docs for the send() method."
1558
     *                       Please use the constructor to set an http protocol variant.
1559
     * @param boolean $fallback deprecated. Was: "when true, upon receiving an error during multicall, multiple single
1560
     *                          calls will be attempted"
1561
     * @return Response[]
1562
     */
1563
    public function multicall($reqs, $timeout = 0, $method = '', $fallback = true)
1564
    {
1565
        // BC
1566
        if (is_bool($timeout) && $fallback === true) {
1567
            $fallback = !$timeout;
1568
            $timeout = 0;
1569
        }
1570
1571
        if ($method == '') {
1572
            $method = $this->method;
1573
        }
1574
1575
        if (!$this->no_multicall) {
1576
            $results = $this->_try_multicall($reqs, $timeout, $method);
1577
            /// @todo how to handle the case of $this->return_type = xml?
1578
            if (is_array($results)) {
1579
                // System.multicall succeeded
1580
                return $results;
1581
            } else {
1582
                // either system.multicall is unsupported by server, or the call failed for some other reason.
1583
                // Feature creep: is there a way to tell apart unsupported multicall from other faults?
1584
                if ($fallback) {
1585
                    // Don't try it next time...
1586
                    $this->no_multicall = true;
1587
                } else {
1588
                    $result = $results;
1589
                }
1590
            }
1591
        } else {
1592
            // override fallback, in case careless user tries to do two
1593
            // opposite things at the same time
1594
            $fallback = true;
1595
        }
1596
1597
        $results = array();
1598
        if ($fallback) {
1599
            // system.multicall is (probably) unsupported by server: emulate multicall via multiple requests
1600
            /// @todo use curl multi_ functions to make this quicker (see the implementation in the parallel.php demo)
1601
            foreach ($reqs as $req) {
1602
                $results[] = $this->send($req, $timeout, $method);
1603
            }
1604
        } else {
1605
            // user does NOT want to fallback on many single calls: since we should always return an array of responses,
1606
            // we return an array with the same error repeated n times
1607
            foreach ($reqs as $req) {
1608
                $results[] = $result;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.
Loading history...
1609
            }
1610
        }
1611
1612
        return $results;
1613
    }
1614
1615
    /**
1616
     * Attempt to boxcar $reqs via system.multicall.
1617
     *
1618
     * @param Request[] $reqs
1619
     * @param int $timeout
1620
     * @param string $method
1621
     * @return Response[]|Response a single Response when the call returned a fault / does not conform to what we expect
1622
     *                             from a multicall response
1623
     */
1624
    private function _try_multicall($reqs, $timeout, $method)
1625
    {
1626
        // Construct multicall request
1627
        $calls = array();
1628
        foreach ($reqs as $req) {
1629
            $call['methodName'] = new Value($req->method(), 'string');
1630
            $numParams = $req->getNumParams();
1631
            $params = array();
1632
            for ($i = 0; $i < $numParams; $i++) {
1633
                $params[$i] = $req->getParam($i);
1634
            }
1635
            $call['params'] = new Value($params, 'array');
1636
            $calls[] = new Value($call, 'struct');
1637
        }
1638
        $multiCall = new static::$requestClass('system.multicall');
1639
        $multiCall->addParam(new Value($calls, 'array'));
1640
1641
        // Attempt RPC call
1642
        $result = $this->send($multiCall, $timeout, $method);
1643
1644
        if ($result->faultCode() != 0) {
1645
            // call to system.multicall failed
1646
            return $result;
1647
        }
1648
1649
        // Unpack responses.
1650
        $rets = $result->value();
1651
        $response = array();
1652
1653
        if ($this->return_type == 'xml') {
1654
            for ($i = 0; $i < count($reqs); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
1655
                $response[] = new static::$responseClass($rets, 0, '', 'xml', $result->httpResponse());
1656
            }
1657
1658
        } elseif ($this->return_type == 'phpvals') {
1659
            if (!is_array($rets)) {
1660
                // bad return type from system.multicall
1661
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1662
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': not an array', 'phpvals', $result->httpResponse());
1663
            }
1664
            $numRets = count($rets);
1665
            if ($numRets != count($reqs)) {
1666
                // wrong number of return values.
1667
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1668
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': incorrect number of responses', 'phpvals',
1669
                    $result->httpResponse());
1670
            }
1671
1672
            for ($i = 0; $i < $numRets; $i++) {
1673
                $val = $rets[$i];
1674
                if (!is_array($val)) {
1675
                    return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1676
                        PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array or struct",
1677
                        'phpvals', $result->httpResponse());
1678
                }
1679
                switch (count($val)) {
1680
                    case 1:
1681
                        if (!isset($val[0])) {
1682
                            // Bad value
1683
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1684
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has no value",
1685
                                'phpvals', $result->httpResponse());
1686
                        }
1687
                        // Normal return value
1688
                        $response[$i] = new static::$responseClass($val[0], 0, '', 'phpvals', $result->httpResponse());
1689
                        break;
1690
                    case 2:
1691
                        /// @todo remove usage of @: it is apparently quite slow
1692
                        $code = @$val['faultCode'];
1693
                        if (!is_int($code)) {
1694
                            /// @todo should we check that it is != 0?
1695
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1696
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1697
                                'phpvals', $result->httpResponse());
1698
                        }
1699
                        $str = @$val['faultString'];
1700
                        if (!is_string($str)) {
1701
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1702
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no FaultString",
1703
                                'phpvals', $result->httpResponse());
1704
                        }
1705
                        $response[$i] = new static::$responseClass(0, $code, $str, 'phpvals', $result->httpResponse());
1706
                        break;
1707
                    default:
1708
                        return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1709
                            PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1710
                            'phpvals', $result->httpResponse());
1711
                }
1712
            }
1713
1714
        } else {
1715
            // return type == 'xmlrpcvals'
1716
            if ($rets->kindOf() != 'array') {
1717
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1718
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array", 'xmlrpcvals',
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $i seems to be defined by a foreach iteration on line 1628. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
1719
                    $result->httpResponse());
1720
            }
1721
            $numRets = $rets->count();
1722
            if ($numRets != count($reqs)) {
1723
                // wrong number of return values.
1724
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1725
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': incorrect number of responses', 'xmlrpcvals',
1726
                    $result->httpResponse());
1727
            }
1728
1729
            foreach ($rets as $i => $val) {
1730
                switch ($val->kindOf()) {
1731
                    case 'array':
1732
                        if ($val->count() != 1) {
1733
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1734
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1735
                                'phpvals', $result->httpResponse());
1736
                        }
1737
                        // Normal return value
1738
                        $response[] = new static::$responseClass($val[0], 0, '', 'xmlrpcvals', $result->httpResponse());
1739
                        break;
1740
                    case 'struct':
1741
                        if ($val->count() != 2) {
1742
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1743
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1744
                                'phpvals', $result->httpResponse());
1745
                        }
1746
                        /** @var Value $code */
1747
                        $code = $val['faultCode'];
1748
                        if ($code->kindOf() != 'scalar' || $code->scalarTyp() != 'int') {
1749
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1750
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1751
                                'xmlrpcvals', $result->httpResponse());
1752
                        }
1753
                        /** @var Value $str */
1754
                        $str = $val['faultString'];
1755
                        if ($str->kindOf() != 'scalar' || $str->scalarTyp() != 'string') {
1756
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1757
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1758
                                'xmlrpcvals', $result->httpResponse());
1759
                        }
1760
                        $response[] = new static::$responseClass(0, $code->scalarVal(), $str->scalarVal(), 'xmlrpcvals', $result->httpResponse());
1761
                        break;
1762
                    default:
1763
                        return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1764
                            PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array or struct",
1765
                            'xmlrpcvals', $result->httpResponse());
1766
                }
1767
            }
1768
        }
1769
1770
        return $response;
1771
    }
1772
1773
    // *** BC layer ***
1774
1775
    /**
1776
     * NB: always goes via socket, never curl
1777
     *
1778
     * @deprecated
1779
     *
1780
     * @param Request $req
1781
     * @param string $server
1782
     * @param int $port
1783
     * @param int $timeout
1784
     * @param string $username
1785
     * @param string $password
1786
     * @param int $authType
1787
     * @param string $proxyHost
1788
     * @param int $proxyPort
1789
     * @param string $proxyUsername
1790
     * @param string $proxyPassword
1791
     * @param int $proxyAuthType
1792
     * @param string $method
1793
     * @return Response
1794
     */
1795
    protected function sendPayloadHTTP10($req, $server, $port, $timeout = 0, $username = '', $password = '',
1796
        $authType = 1, $proxyHost = '', $proxyPort = 0, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1,
1797
        $method = 'http')
1798
    {
1799
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
1800
1801
        return $this->sendPayloadSocket($req, $server, $port, $timeout, $username, $password, $authType, null, null,
0 ignored issues
show
Deprecated Code introduced by
The function PhpXmlRpc\Client::sendPayloadSocket() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1801
        return /** @scrutinizer ignore-deprecated */ $this->sendPayloadSocket($req, $server, $port, $timeout, $username, $password, $authType, null, null,
Loading history...
1802
            null, null, $proxyHost, $proxyPort, $proxyUsername, $proxyPassword, $proxyAuthType, $method);
1803
    }
1804
1805
    /**
1806
     * NB: always goes via curl, never socket
1807
     *
1808
     * @deprecated
1809
     *
1810
     * @param Request $req
1811
     * @param string $server
1812
     * @param int $port
1813
     * @param int $timeout
1814
     * @param string $username
1815
     * @param string $password
1816
     * @param int $authType
1817
     * @param string $cert
1818
     * @param string $certPass
1819
     * @param string $caCert
1820
     * @param string $caCertDir
1821
     * @param string $proxyHost
1822
     * @param int $proxyPort
1823
     * @param string $proxyUsername
1824
     * @param string $proxyPassword
1825
     * @param int $proxyAuthType
1826
     * @param bool $keepAlive
1827
     * @param string $key
1828
     * @param string $keyPass
1829
     * @param int $sslVersion
1830
     * @return Response
1831
     */
1832
    protected function sendPayloadHTTPS($req, $server, $port, $timeout = 0, $username = '', $password = '',
1833
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
1834
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $keepAlive = false, $key = '', $keyPass = '',
1835
        $sslVersion = 0)
1836
    {
1837
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
1838
1839
        return $this->sendPayloadCURL($req, $server, $port, $timeout, $username,
0 ignored issues
show
Deprecated Code introduced by
The function PhpXmlRpc\Client::sendPayloadCURL() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1839
        return /** @scrutinizer ignore-deprecated */ $this->sendPayloadCURL($req, $server, $port, $timeout, $username,
Loading history...
1840
            $password, $authType, $cert, $certPass, $caCert, $caCertDir, $proxyHost, $proxyPort,
1841
            $proxyUsername, $proxyPassword, $proxyAuthType, 'https', $keepAlive, $key, $keyPass, $sslVersion);
1842
    }
1843
1844
    /**
1845
     * @deprecated
1846
     *
1847
     * @param Request $req
1848
     * @param string $server
1849
     * @param int $port
1850
     * @param int $timeout
1851
     * @param string $username
1852
     * @param string $password
1853
     * @param int $authType only value supported is 1
1854
     * @param string $cert
1855
     * @param string $certPass
1856
     * @param string $caCert
1857
     * @param string $caCertDir
1858
     * @param string $proxyHost
1859
     * @param int $proxyPort
1860
     * @param string $proxyUsername
1861
     * @param string $proxyPassword
1862
     * @param int $proxyAuthType only value supported is 1
1863
     * @param string $method 'http' (synonym for 'http10'), 'http10' or 'https'
1864
     * @param string $key
1865
     * @param string $keyPass @todo not implemented yet.
1866
     * @param int $sslVersion
1867
     * @return Response
1868
     */
1869
    protected function sendPayloadSocket($req, $server, $port, $timeout = 0, $username = '', $password = '',
1870
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
1871
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'http', $key = '', $keyPass = '',
1872
        $sslVersion = 0)
1873
    {
1874
        $this->logDeprecationUnlessCalledBy('send');
1875
1876
        return $this->sendViaSocket($req, $method, $server, $port, $this->path, array(
1877
            'accepted_charset_encodings' => $this->accepted_charset_encodings,
1878
            'accepted_compression' => $this->accepted_compression,
1879
            'authtype' => $authType,
1880
            'cacert' => $caCert,
1881
            'cacertdir' => $caCertDir,
1882
            'cert' => $cert,
1883
            'certpass' => $certPass,
1884
            'cookies' => $this->cookies,
1885
            'debug' => $this->debug,
1886
            'extracurlopts' => $this->extracurlopts,
1887
            'extrasockopts' => $this->extrasockopts,
1888
            'keepalive' => $this->keepalive,
1889
            'key' => $key,
1890
            'keypass' => $keyPass,
1891
            'no_multicall' => $this->no_multicall,
1892
            'password' => $password,
1893
            'proxy' => $proxyHost,
1894
            'proxy_authtype' => $proxyAuthType,
1895
            'proxy_pass' => $proxyPassword,
1896
            'proxyport' => $proxyPort,
1897
            'proxy_user' => $proxyUsername,
1898
            'request_charset_encoding' => $this->request_charset_encoding,
1899
            'request_compression' => $this->request_compression,
1900
            'return_type' => $this->return_type,
1901
            'sslversion' => $sslVersion,
1902
            'timeout' => $timeout,
1903
            'username' => $username,
1904
            'user_agent' => $this->user_agent,
1905
            'use_curl' => $this->use_curl,
1906
            'verifyhost' => $this->verifyhost,
1907
            'verifypeer' => $this->verifypeer,
1908
        ));
1909
    }
1910
1911
    /**
1912
     * @deprecated
1913
     *
1914
     * @param Request $req
1915
     * @param string $server
1916
     * @param int $port
1917
     * @param int $timeout
1918
     * @param string $username
1919
     * @param string $password
1920
     * @param int $authType
1921
     * @param string $cert
1922
     * @param string $certPass
1923
     * @param string $caCert
1924
     * @param string $caCertDir
1925
     * @param string $proxyHost
1926
     * @param int $proxyPort
1927
     * @param string $proxyUsername
1928
     * @param string $proxyPassword
1929
     * @param int $proxyAuthType
1930
     * @param string $method 'http' (let curl decide), 'http10', 'http11', 'https', 'h2c' or 'h2'
1931
     * @param bool $keepAlive
1932
     * @param string $key
1933
     * @param string $keyPass
1934
     * @param int $sslVersion
1935
     * @return Response
1936
     */
1937
    protected function sendPayloadCURL($req, $server, $port, $timeout = 0, $username = '', $password = '',
1938
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
1939
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'https', $keepAlive = false, $key = '',
1940
        $keyPass = '', $sslVersion = 0)
1941
    {
1942
        $this->logDeprecationUnlessCalledBy('send');
1943
1944
        return $this->sendViaCURL($req, $method, $server, $port, $this->path, array(
1945
            'accepted_charset_encodings' => $this->accepted_charset_encodings,
1946
            'accepted_compression' => $this->accepted_compression,
1947
            'authtype' => $authType,
1948
            'cacert' => $caCert,
1949
            'cacertdir' => $caCertDir,
1950
            'cert' => $cert,
1951
            'certpass' => $certPass,
1952
            'cookies' => $this->cookies,
1953
            'debug' => $this->debug,
1954
            'extracurlopts' => $this->extracurlopts,
1955
            'extrasockopts' => $this->extrasockopts,
1956
            'keepalive' => $keepAlive,
1957
            'key' => $key,
1958
            'keypass' => $keyPass,
1959
            'no_multicall' => $this->no_multicall,
1960
            'password' => $password,
1961
            'proxy' => $proxyHost,
1962
            'proxy_authtype' => $proxyAuthType,
1963
            'proxy_pass' => $proxyPassword,
1964
            'proxyport' => $proxyPort,
1965
            'proxy_user' => $proxyUsername,
1966
            'request_charset_encoding' => $this->request_charset_encoding,
1967
            'request_compression' => $this->request_compression,
1968
            'return_type' => $this->return_type,
1969
            'sslversion' => $sslVersion,
1970
            'timeout' => $timeout,
1971
            'username' => $username,
1972
            'user_agent' => $this->user_agent,
1973
            'use_curl' => $this->use_curl,
1974
            'verifyhost' => $this->verifyhost,
1975
            'verifypeer' => $this->verifypeer,
1976
        ));
1977
    }
1978
1979
    /**
1980
     * @deprecated
1981
     *
1982
     * @param $req
1983
     * @param $server
1984
     * @param $port
1985
     * @param $timeout
1986
     * @param $username
1987
     * @param $password
1988
     * @param $authType
1989
     * @param $cert
1990
     * @param $certPass
1991
     * @param $caCert
1992
     * @param $caCertDir
1993
     * @param $proxyHost
1994
     * @param $proxyPort
1995
     * @param $proxyUsername
1996
     * @param $proxyPassword
1997
     * @param $proxyAuthType
1998
     * @param $method
1999
     * @param $keepAlive
2000
     * @param $key
2001
     * @param $keyPass
2002
     * @param $sslVersion
2003
     * @return false|\CurlHandle|resource
2004
     */
2005
    protected function prepareCurlHandle($req, $server, $port, $timeout = 0, $username = '', $password = '',
2006
         $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
2007
         $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'https', $keepAlive = false, $key = '',
2008
         $keyPass = '', $sslVersion = 0)
2009
    {
2010
        $this->logDeprecationUnlessCalledBy('sendViaCURL');
2011
2012
        return $this->createCURLHandle($req, $method, $server, $port, $this->path, array(
2013
            'accepted_charset_encodings' => $this->accepted_charset_encodings,
2014
            'accepted_compression' => $this->accepted_compression,
2015
            'authtype' => $authType,
2016
            'cacert' => $caCert,
2017
            'cacertdir' => $caCertDir,
2018
            'cert' => $cert,
2019
            'certpass' => $certPass,
2020
            'cookies' => $this->cookies,
2021
            'debug' => $this->debug,
2022
            'extracurlopts' => $this->extracurlopts,
2023
            'keepalive' => $keepAlive,
2024
            'key' => $key,
2025
            'keypass' => $keyPass,
2026
            'no_multicall' => $this->no_multicall,
2027
            'password' => $password,
2028
            'proxy' => $proxyHost,
2029
            'proxy_authtype' => $proxyAuthType,
2030
            'proxy_pass' => $proxyPassword,
2031
            'proxyport' => $proxyPort,
2032
            'proxy_user' => $proxyUsername,
2033
            'request_charset_encoding' => $this->request_charset_encoding,
2034
            'request_compression' => $this->request_compression,
2035
            'return_type' => $this->return_type,
2036
            'sslversion' => $sslVersion,
2037
            'timeout' => $timeout,
2038
            'username' => $username,
2039
            'user_agent' => $this->user_agent,
2040
            'use_curl' => $this->use_curl,
2041
            'verifyhost' => $this->verifyhost,
2042
            'verifypeer' => $this->verifypeer,
2043
        ));
2044
    }
2045
2046
    // we have to make this return by ref in order to allow calls such as `$resp->_cookies['name'] = ['value' => 'something'];`
2047
    public function &__get($name)
2048
    {
2049
        if (in_array($name, static::$options)) {
2050
            $this->logDeprecation('Getting property Client::' . $name . ' is deprecated');
2051
            return $this->$name;
2052
        }
2053
2054
        switch ($name) {
2055
            case 'errno':
2056
            case 'errstr':
2057
            case 'method':
2058
            case 'server':
2059
            case 'port':
2060
            case 'path':
2061
                $this->logDeprecation('Getting property Client::' . $name . ' is deprecated');
2062
                return $this->$name;
2063
            default:
2064
                /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout...
2065
                $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
2066
                trigger_error('Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
2067
                $result = null;
2068
                return $result;
2069
        }
2070
    }
2071
2072
    public function __set($name, $value)
2073
    {
2074
        if (in_array($name, static::$options)) {
2075
            $this->logDeprecation('Setting property Client::' . $name . ' is deprecated');
2076
            $this->$name = $value;
2077
            return;
2078
        }
2079
2080
        switch ($name) {
2081
            case 'errno':
2082
            case 'errstr':
2083
            case 'method':
2084
            case 'server':
2085
            case 'port':
2086
            case 'path':
2087
                $this->logDeprecation('Setting property Client::' . $name . ' is deprecated');
2088
                $this->$name = $value;
2089
                return;
2090
            default:
2091
                /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout...
2092
                $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
2093
                trigger_error('Undefined property via __set(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
2094
        }
2095
    }
2096
2097
    public function __isset($name)
2098
    {
2099
        if (in_array($name, static::$options)) {
2100
            $this->logDeprecation('Checking property Client::' . $name . ' is deprecated');
2101
            return isset($this->$name);
2102
        }
2103
2104
        switch ($name) {
2105
            case 'errno':
2106
            case 'errstr':
2107
            case 'method':
2108
            case 'server':
2109
            case 'port':
2110
            case 'path':
2111
                $this->logDeprecation('Checking property Client::' . $name . ' is deprecated');
2112
                return isset($this->$name);
2113
            default:
2114
                return false;
2115
        }
2116
    }
2117
2118
    public function __unset($name)
2119
    {
2120
        if (in_array($name, static::$options)) {
2121
            $this->logDeprecation('Unsetting property Client::' . $name . ' is deprecated');
2122
            unset($this->$name);
2123
            return;
2124
        }
2125
2126
        switch ($name) {
2127
            case 'errno':
2128
            case 'errstr':
2129
            case 'method':
2130
            case 'server':
2131
            case 'port':
2132
            case 'path':
2133
                $this->logDeprecation('Unsetting property Client::' . $name . ' is deprecated');
2134
                unset($this->$name);
2135
                return;
2136
            default:
2137
                /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout...
2138
                $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
2139
                trigger_error('Undefined property via __unset(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
2140
        }
2141
    }
2142
}
2143