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
61
    /** @var string */
62
    protected static $requestClass = '\\PhpXmlRpc\\Request';
63
    /** @var string */
64
    protected static $responseClass = '\\PhpXmlRpc\\Response';
65
66
    /**
67
     * @var int
68
     * @deprecated will be removed in the future
69
     */
70
    protected $errno;
71
    /**
72
     * @var string
73
     * @deprecated will be removed in the future
74
     */
75
    protected $errstr;
76
77
    /// @todo: do all the ones below need to be public?
78
79
    /**
80
     * @var string
81
     */
82
    protected $method = 'http';
83
    /**
84
     * @var string
85
     */
86
    protected $server;
87
    /**
88
     * @var int
89
     */
90
    protected $port = 0;
91
    /**
92
     * @var string
93
     */
94
    protected $path;
95
96
    /**
97
     * @var int
98
     */
99
    protected $debug = 0;
100
    /**
101
     * @var string
102
     */
103
    protected $username = '';
104
    /**
105
     * @var string
106
     */
107
    protected $password = '';
108
    /**
109
     * @var int
110
     */
111
    protected $authtype = 1;
112
    /**
113
     * @var string
114
     */
115
    protected $cert = '';
116
    /**
117
     * @var string
118
     */
119
    protected $certpass = '';
120
    /**
121
     * @var string
122 2
     */
123
    protected $cacert = '';
124 2
    /**
125 2
     * @var string
126
     */
127 2
    protected $cacertdir = '';
128
    /**
129
     * @var string
130
     */
131
    protected $key = '';
132
    /**
133
     * @var string
134
     */
135
    protected $keypass = '';
136
    /**
137
     * @var bool
138
     */
139
    protected $verifypeer = true;
140
    /**
141
     * @var int
142
     */
143
    protected $verifyhost = 2;
144
    /**
145
     * @var int
146
     */
147
    protected $sslversion = 0; // corresponds to CURL_SSLVERSION_DEFAULT. Other  CURL_SSLVERSION_ values are supported
148
    /**
149
     * @var string
150
     */
151
    protected $proxy = '';
152 718
    /**
153
     * @var int
154
     */
155 718
    protected $proxyport = 0;
156 7
    /**
157 7
     * @var string
158 7
     */
159 7
    protected $proxy_user = '';
160
    /**
161
     * @var string
162 7
     */
163
    protected $proxy_pass = '';
164
    /**
165 7
     * @var int
166
     */
167
    protected $proxy_authtype = 1;
168 7
    /**
169 7
     * @var array
170
     */
171 7
    protected $cookies = array();
172
    /**
173
     * @var array
174 7
     */
175
    protected $extrasockopts = array();
176
    /**
177
     * @var array
178 718
     */
179
    protected $extracurlopts = array();
180
    /**
181 718
     * @var int
182
     */
183 718
    protected $timeout = 0;
184 718
    /**
185 3
     * @var int
186
     */
187 718
    protected $use_curl = self::USE_CURL_AUTO;
188 7
    /**
189
     * @var bool
190
     *
191
     * This determines whether the multicall() method will try to take advantage of the system.multicall xml-rpc method
192 718
     * to dispatch to the server an array of requests in a single http roundtrip or simply execute many consecutive http
193
     * calls. Defaults to FALSE, but it will be enabled automatically on the first failure of execution of
194 711
     * system.multicall.
195
     */
196
    protected $no_multicall = false;
197 718
    /**
198
     * @var array
199
     *
200
     * List of http compression methods accepted by the client for responses.
201 718
     * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib.
202
     *
203
     * 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
204 718
     * decide the compression methods it supports. You might check for the presence of 'zlib' in the output of
205
     * curl_version() to determine whether compression is supported or not
206
     */
207
    protected $accepted_compression = array();
208
    /**
209
     * @var string|null
210
     *
211
     * Name of compression scheme to be used for sending requests.
212
     * Either null, 'gzip' or 'deflate'.
213
     */
214
    protected $request_compression = '';
215
    /**
216
     * @var bool
217 718
     *
218 718
     * Whether to use persistent connections for http 1.1 and https. Value set at constructor time.
219
     */
220
    protected $keepalive = false;
221
    /**
222
     * @var string[]
223
     *
224
     * Charset encodings that can be decoded without problems by the client. Value set at constructor time
225
     */
226
    protected $accepted_charset_encodings = array();
227
    /**
228
     * @var string
229
     *
230
     * The charset encoding that will be used for serializing request sent by the client.
231
     * It defaults to NULL, which means using US-ASCII and encoding all characters outside the ASCII printable range
232
     * using their xml character entity representation (this has the benefit that line end characters will not be mangled
233 714
     * in the transfer, a CR-LF will be preserved as well as a singe LF).
234
     * Valid values are 'US-ASCII', 'UTF-8' and 'ISO-8859-1'.
235 714
     * For the fastest mode of operation, set your both your app internal encoding and this to UTF-8.
236 714
     */
237
    protected $request_charset_encoding = '';
238
    /**
239
     * @var string
240
     *
241
     * Decides the content of Response objects returned by calls to send() and multicall().
242
     * Valid values are 'xmlrpcvals', 'phpvals' or 'xml'.
243
     *
244
     * Determines whether the value returned inside a Response object as results of calls to the send() and multicall()
245
     * methods will be a Value object, a plain php value or a raw xml string.
246
     * Allowed values are 'xmlrpcvals' (the default), 'phpvals' and 'xml'.
247
     * To allow the user to differentiate between a correct and a faulty response, fault responses will be returned as
248
     * Response objects in any case.
249
     * Note that the 'phpvals' setting will yield faster execution times, but some of the information from the original
250
     * response will be lost. It will be e.g. impossible to tell whether a particular php string value was sent by the
251
     * server as an xml-rpc string or base64 value.
252 66
     */
253
    protected $return_type = XMLParser::RETURN_XMLRPCVALS;
254 66
    /**
255 66
     * @var string
256 66
     *
257 66
     * Sent to servers in http headers. Value set at constructor time.
258
     */
259
    protected $user_agent;
260
261
    /**
262
     * CURL handle: used for keep-alive
263
     * @internal
264
     */
265
    public $xmlrpc_curl_handle = null;
266
267
    /**
268
     * @var array
269
     */
270
    protected static $options = array(
271
        self::OPT_ACCEPTED_CHARSET_ENCODINGS,
272
        self::OPT_ACCEPTED_COMPRESSION,
273
        self::OPT_AUTH_TYPE,
274
        self::OPT_CA_CERT,
275
        self::OPT_CA_CERT_DIR,
276
        self::OPT_CERT,
277
        self::OPT_CERT_PASS,
278
        self::OPT_COOKIES,
279
        self::OPT_DEBUG,
280
        self::OPT_EXTRA_CURL_OPTS,
281
        self::OPT_EXTRA_SOCKET_OPTS,
282
        self::OPT_KEEPALIVE,
283
        self::OPT_KEY,
284
        self::OPT_KEY_PASS,
285
        self::OPT_NO_MULTICALL,
286
        self::OPT_PASSWORD,
287
        self::OPT_PROXY,
288
        self::OPT_PROXY_AUTH_TYPE,
289
        self::OPT_PROXY_PASS,
290
        self::OPT_PROXY_USER,
291
        self::OPT_PROXY_PORT,
292
        self::OPT_REQUEST_CHARSET_ENCODING,
293
        self::OPT_REQUEST_COMPRESSION,
294
        self::OPT_RETURN_TYPE,
295
        self::OPT_SSL_VERSION,
296
        self::OPT_TIMEOUT,
297
        self::OPT_USE_CURL,
298
        self::OPT_USER_AGENT,
299
        self::OPT_USERNAME,
300
        self::OPT_VERIFY_HOST,
301
        self::OPT_VERIFY_PEER,
302
    );
303
304
    /**
305
     * @param string $path either the PATH part of the xml-rpc server URL, or complete server URL (in which case you
306
     *                     should use an empty string for all other parameters)
307
     *                     e.g. /xmlrpc/server.php
308
     *                     e.g. http://phpxmlrpc.sourceforge.net/server.php
309
     *                     e.g. https://james:[email protected]:444/xmlrpcserver?agent=007
310
     *                     e.g. h2://fast-and-secure-services.org/endpoint
311
     * @param string $server the server name / ip address
312
     * @param integer $port the port the server is listening on, when omitted defaults to 80 or 443 depending on
313
     *                      protocol used
314
     * @param string $method the http protocol variant: defaults to 'http'; 'https', 'http11', 'h2' and 'h2c' can
315
     *                       be used if CURL is installed. The value set here can be overridden in any call to $this->send().
316 132
     *                       Use 'h2' to make the lib attempt to use http/2 over a secure connection, and 'h2c'
317
     *                       for http/2 without tls. Note that 'h2c' will not use the h2c 'upgrade' method, and be
318 132
     *                       thus incompatible with any server/proxy not supporting http/2. This is because POST
319 132
     *                       request are not compatible with h2c upgrade.
320
     */
321
    public function __construct($path, $server = '', $port = '', $method = '')
322
    {
323
        // allow user to specify all params in $path
324
        if ($server == '' && $port == '' && $method == '') {
325
            $parts = parse_url($path);
326
            $server = $parts['host'];
327
            $path = isset($parts['path']) ? $parts['path'] : '';
328 132
            if (isset($parts['query'])) {
329
                $path .= '?' . $parts['query'];
330 132
            }
331 132
            if (isset($parts['fragment'])) {
332
                $path .= '#' . $parts['fragment'];
333
            }
334
            if (isset($parts['port'])) {
335
                $port = $parts['port'];
336
            }
337
            if (isset($parts['scheme'])) {
338 132
                $method = $parts['scheme'];
339
            }
340 132
            if (isset($parts['user'])) {
341 132
                $this->username = $parts['user'];
342
            }
343
            if (isset($parts['pass'])) {
344
                $this->password = $parts['pass'];
345
            }
346
        }
347
        if ($path == '' || $path[0] != '/') {
348
            $this->path = '/' . $path;
349
        } else {
350
            $this->path = $path;
351
        }
352
        $this->server = $server;
353
        if ($port != '') {
354
            $this->port = $port;
355 99
        }
356
        if ($method != '') {
357 99
            $this->method = $method;
358 99
        }
359 99
360 99
        // if ZLIB is enabled, let the client by default accept compressed responses
361 99
        if (function_exists('gzinflate') || (
362 99
                function_exists('curl_version') && (($info = curl_version()) &&
363
                    ((is_string($info) && strpos($info, 'zlib') !== null) || isset($info['libz_version'])))
364
            )
365
        ) {
366
            $this->accepted_compression = array('gzip', 'deflate');
367
        }
368
369
        // keepalives: enabled by default
370
        $this->keepalive = true;
371
372
        // by default the xml parser can support these 3 charset encodings
373
        $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII');
374
375
        // NB: this is disabled to avoid making all the requests sent huge... mbstring supports more than 80 charsets!
376
        //$ch = $this->getCharsetEncoder();
377
        //$this->accepted_charset_encodings = $ch->knownCharsets();
378
379
        // initialize user_agent string
380
        $this->user_agent = PhpXmlRpc::$xmlrpcName . ' ' . PhpXmlRpc::$xmlrpcVersion;
381
    }
382
383
    /**
384
     * @param string $name see all the OPT_ constants
385
     * @param mixed $value
386
     * @return $this
387
     * @throws ValueErrorException on unsupported option
388
     */
389
    public function setOption($name, $value)
390
    {
391
        if (in_array($name, static::$options)) {
392
            $this->$name = $value;
393
            return $this;
394
        }
395
396
        throw new ValueErrorException("Unsupported option '$name'");
397
    }
398
399
    /**
400
     * @param string $name see all the OPT_ constants
401
     * @return mixed
402
     * @throws ValueErrorException on unsupported option
403
     */
404
    public function getOption($name)
405
    {
406
        if (in_array($name, static::$options)) {
407
            return $this->$name;
408
        }
409
410
        throw new ValueErrorException("Unsupported option '$name'");
411
    }
412
413
    /**
414
     * Returns the complete list of Client options, with their value.
415
     * @return array
416
     */
417
    public function getOptions()
418 580
    {
419
        $values = array();
420 580
        foreach (static::$options as $opt) {
421 580
            $values[$opt] = $this->getOption($opt);
422
        }
423
        return $values;
424
    }
425
426
    /**
427 580
     * @param array $options key: any valid option (see all the OPT_ constants)
428
     * @return $this
429 580
     * @throws ValueErrorException on unsupported option
430
     */
431
    public function setOptions($options)
432
    {
433
        foreach ($options as $name => $value) {
434
            $this->setOption($name, $value);
435
        }
436
437
        return $this;
438
    }
439
440
    /**
441
     * Enable/disable the echoing to screen of the xml-rpc responses received. The default is not to output anything.
442
     *
443
     * The debugging information at level 1 includes the raw data returned from the XML-RPC server it was querying
444
     * (including bot HTTP headers and the full XML payload), and the PHP value the client attempts to create to
445
     * represent the value returned by the server.
446 66
     * At level 2, the complete payload of the xml-rpc request is also printed, before being sent to the server.
447
     * At level -1, the Response objects returned by send() calls will not carry information about the http response's
448 66
     * cookies, headers and body, which might save some memory
449 66
     *
450
     * This option can be very useful when debugging servers as it allows you to see exactly what the client sends and
451
     * the server returns. Never leave it enabled for production!
452
     *
453
     * @param integer $level values -1, 0, 1 and 2 are supported
454
     * @return $this
455
     */
456
    public function setDebug($level)
457
    {
458
        $this->debug = $level;
459
        return $this;
460
    }
461
462
    /**
463
     * Sets the username and password for authorizing the client to the server.
464
     *
465
     * With the default (HTTP) transport, this information is used for HTTP Basic authorization.
466
     * Note that username and password can also be set using the class constructor.
467
     * With HTTP 1.1 and HTTPS transport, NTLM and Digest authentication protocols are also supported. To enable them use
468
     * the constants CURLAUTH_DIGEST and CURLAUTH_NTLM as values for the auth type parameter.
469
     *
470
     * @param string $user username
471
     * @param string $password password
472
     * @param integer $authType auth type. See curl_setopt man page for supported auth types. Defaults to CURLAUTH_BASIC
473
     *                          (basic auth). Note that auth types NTLM and Digest will only work if the Curl php
474
     *                          extension is enabled.
475
     * @return $this
476
     */
477
    public function setCredentials($user, $password, $authType = 1)
478
    {
479
        $this->username = $user;
480
        $this->password = $password;
481
        $this->authtype = $authType;
482
        return $this;
483
    }
484
485
    /**
486
     * Set the optional certificate and passphrase used in SSL-enabled communication with a remote server.
487
     *
488
     * Note: to retrieve information about the client certificate on the server side, you will need to look into the
489
     * environment variables which are set up by the webserver. Different webservers will typically set up different
490
     * variables.
491
     *
492
     * @param string $cert the name of a file containing a PEM formatted certificate
493
     * @param string $certPass the password required to use it
494
     * @return $this
495 697
     */
496
    public function setCertificate($cert, $certPass = '')
497
    {
498
        $this->cert = $cert;
499 697
        $this->certpass = $certPass;
500 117
        return $this;
501
    }
502
503 697
    /**
504
     * Add a CA certificate to verify server with in SSL-enabled communication when SetSSLVerifypeer has been set to TRUE.
505 66
     *
506
     * See the php manual page about CURLOPT_CAINFO for more details.
507 66
     *
508 697
     * @param string $caCert certificate file name (or dir holding certificates)
509 28
     * @param bool $isDir set to true to indicate cacert is a dir. defaults to false
510 28
     * @return $this
511 28
     */
512
    public function setCaCertificate($caCert, $isDir = false)
513
    {
514
        if ($isDir) {
515 697
            $this->cacertdir = $caCert;
516
        } else {
517
            $this->cacert = $caCert;
518
        }
519 697
        return $this;
520 697
    }
521
522 697
    /**
523 322
     * Set attributes for SSL communication: private SSL key.
524 322
     *
525 322
     * NB: does not work in older php/curl installs.
526 322
     * Thanks to Daniel Convissor.
527
     *
528 322
     * @param string $key The name of a file containing a private SSL key
529 322
     * @param string $keyPass The secret password needed to use the private SSL key
530 322
     * @return $this
531 322
     */
532 322
    public function setKey($key, $keyPass)
533 322
    {
534 322
        $this->key = $key;
535 322
        $this->keypass = $keyPass;
536 322
        return $this;
537 322
    }
538 322
539 322
    /**
540
     * Set attributes for SSL communication: verify the remote host's SSL certificate, and cause the connection to fail
541 322
     * if the cert verification fails.
542 322
     *
543 322
     * By default, verification is enabled.
544 322
     * To specify custom SSL certificates to validate the server with, use the setCaCertificate method.
545 322
     *
546
     * @param bool $i enable/disable verification of peer certificate
547
     * @return $this
548
     * @deprecated use setOption
549 375
     */
550 375
    public function setSSLVerifyPeer($i)
551 375
    {
552 375
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
553
554 375
        $this->verifypeer = $i;
555 375
        return $this;
556 375
    }
557 375
558 375
    /**
559 375
     * Set attributes for SSL communication: verify the remote host's SSL certificate's common name (CN).
560 375
     *
561 375
     * Note that support for value 1 has been removed in cURL 7.28.1
562 375
     *
563 375
     * @param int $i Set to 1 to only the existence of a CN, not that it matches
564 375
     * @return $this
565 375
     * @deprecated use setOption
566
     */
567 375
    public function setSSLVerifyHost($i)
568 375
    {
569 375
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
570
571
        $this->verifyhost = $i;
572
        return $this;
573 697
    }
574
575
    /**
576
     * Set attributes for SSL communication: SSL version to use. Best left at 0 (default value): let cURL decide
577
     *
578
     * @param int $i see  CURL_SSLVERSION_ constants
579
     * @return $this
580
     * @deprecated use setOption
581
     */
582
    public function setSSLVersion($i)
583
    {
584
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
585
586
        $this->sslversion = $i;
587
        return $this;
588
    }
589
590
    /**
591
     * Set proxy info.
592
     *
593
     * NB: CURL versions before 7.11.10 cannot use a proxy to communicate with https servers.
594
     *
595
     * @param string $proxyHost
596
     * @param string $proxyPort Defaults to 8080 for HTTP and 443 for HTTPS
597
     * @param string $proxyUsername Leave blank if proxy has public access
598
     * @param string $proxyPassword Leave blank if proxy has public access
599
     * @param int $proxyAuthType defaults to CURLAUTH_BASIC (Basic authentication protocol); set to constant CURLAUTH_NTLM
600
     *                           to use NTLM auth with proxy (has effect only when the client uses the HTTP 1.1 protocol)
601
     * @return $this
602
     */
603
    public function setProxy($proxyHost, $proxyPort, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1)
604
    {
605
        $this->proxy = $proxyHost;
606
        $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...
607
        $this->proxy_user = $proxyUsername;
608
        $this->proxy_pass = $proxyPassword;
609
        $this->proxy_authtype = $proxyAuthType;
610
        return $this;
611
    }
612
613
    /**
614
     * Enables/disables reception of compressed xml-rpc responses.
615
     *
616
     * This requires the "zlib" extension to be enabled in your php install. If it is, by default xmlrpc_client
617
     * instances will enable reception of compressed content.
618
     * Note that enabling reception of compressed responses merely adds some standard http headers to xml-rpc requests.
619
     * It is up to the xml-rpc server to return compressed responses when receiving such requests.
620
     *
621
     * @param string $compMethod either 'gzip', 'deflate', 'any' or ''
622
     * @return $this
623
     */
624
    public function setAcceptedCompression($compMethod)
625
    {
626
        if ($compMethod == 'any') {
627
            $this->accepted_compression = array('gzip', 'deflate');
628
        } 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...
629
            $this->accepted_compression = array();
630
        } else {
631
            $this->accepted_compression = array($compMethod);
632
        }
633
        return $this;
634
    }
635
636
    /**
637
     * Enables/disables http compression of xml-rpc request.
638
     *
639
     * This requires the "zlib" extension to be enabled in your php install.
640
     * Take care when sending compressed requests: servers might not support them (and automatic fallback to
641
     * uncompressed requests is not yet implemented).
642
     *
643
     * @param string $compMethod either 'gzip', 'deflate' or ''
644
     * @return $this
645
     * @deprecated use setOption
646
     */
647
    public function setRequestCompression($compMethod)
648
    {
649
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
650
651
        $this->request_compression = $compMethod;
652
        return $this;
653
    }
654
655
    /**
656
     * Adds a cookie to list of cookies that will be sent to server with every further request (useful e.g. for keeping
657
     * session info outside the xml-rpc payload).
658
     *
659
     * NB: by default all cookies set via this method are sent to the server, regardless of path/domain/port. Taking
660
     * advantage of those values is left to the single developer.
661
     *
662
     * @param string $name nb: will not be escaped in the request's http headers. Take care not to use CTL chars or
663
     *                     separators!
664 375
     * @param string $value
665
     * @param string $path
666
     * @param string $domain
667
     * @param int $port do not use! Cookies are not separated by port
668
     * @return $this
669
     *
670
     * @todo check correctness of urlencoding cookie value (copied from php way of doing it, but php is generally sending
671 375
     *       response not requests. We do the opposite...)
672 373
     * @todo strip invalid chars from cookie name? As per RFC 6265, we should follow RFC 2616, Section 2.2
673
     * @todo drop/rename $port parameter. Cookies are not isolated by port!
674
     * @todo feature-creep allow storing 'expires', 'secure', 'httponly' and 'samesite' cookie attributes (we could do
675
     *       as php, and allow $path to be an array of attributes...)
676 375
     */
677 346
    public function setCookie($name, $value = '', $path = '', $domain = '', $port = null)
678
    {
679
        $this->cookies[$name]['value'] = rawurlencode($value);
680 375
        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...
681
            $this->cookies[$name]['path'] = $path;
682 375
            $this->cookies[$name]['domain'] = $domain;
683 375
            $this->cookies[$name]['port'] = $port;
684 64
        }
685 32
        return $this;
686 32
    }
687 32
688 32
    /**
689
     * Directly set cURL options, for extra flexibility (when in cURL mode).
690
     *
691 32
     * It allows e.g. to bind client to a specific IP interface / address.
692 32
     *
693 32
     * @param array $options
694 32
     * @return $this
695
     * @deprecated use setOption
696
     */
697
    public function setCurlOptions($options)
698
    {
699
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
700 375
701 375
        $this->extracurlopts = $options;
702 32
        return $this;
703 32
    }
704
705
    /**
706
     * @param int $useCurlMode self::USE_CURL_ALWAYS, self::USE_CURL_AUTO or self::USE_CURL_NEVER
707
     * @return $this
708 375
     * @deprecated use setOption
709 375
     */
710 73
    public function setUseCurl($useCurlMode)
711
    {
712
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
713 375
714 375
        $this->use_curl = $useCurlMode;
715 32
        return $this;
716
    }
717
718 32
719 32
    /**
720 32
     * Set user-agent string that will be used by this client instance in http headers sent to the server.
721 32
     *
722 32
     * The default user agent string includes the name of this library and the version number.
723
     *
724
     * @param string $agentString
725
     * @return $this
726 32
     * @deprecated use setOption
727
     */
728
    public function setUserAgent($agentString)
729 343
    {
730 343
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
731 343
732 343
        $this->user_agent = $agentString;
733
        return $this;
734
    }
735
736 375
    /**
737 375
     * @param null|int $component allowed values: PHP_URL_SCHEME, PHP_URL_HOST, PHP_URL_PORT, PHP_URL_PATH
738 309
     * @return string|int Notes: the path component will include query string and fragment; NULL is a valid value for port
739 309
     *                    (in which case the default port for http/https will be used);
740 309
     * @throws ValueErrorException on unsupported component
741
     */
742
    public function getUrl($component = null)
743
    {
744
        if (is_int($component) || ctype_digit($component)) {
745
            switch ($component) {
746
                case PHP_URL_SCHEME:
747
                    return $this->method;
748
                case PHP_URL_HOST:
749
                    return $this->server;
750
                case PHP_URL_PORT:
751
                    return $this->port;
752
                case  PHP_URL_PATH:
753 309
                    return $this->path;
754
                case '':
755
756 309
                default:
757
                    throw new ValueErrorException("Unsupported component '$component'");
758
            }
759
        }
760 375
761 375
        $url = $this->method . '://' . $this->server;
762
        if ($this->port == 0 || ($this->port == 80 && in_array($this->method, array('http', 'http10', 'http11', 'h2c'))) ||
763
            ($this->port == 443 && in_array($this->method, array('https', 'h2')))) {
764
            return $url . $this->path;
765
        } else {
766 375
            return $url . ':' . $this->port . $this->path;
767 375
        }
768 375
    }
769 375
770 375
    /**
771 375
     * Send an xml-rpc request to the server.
772 375
     *
773 375
     * @param Request|Request[]|string $req The Request object, or an array of requests for using multicall, or the
774 375
     *                                      complete xml representation of a request.
775 375
     *                                      When sending an array of Request objects, the client will try to make use of
776 375
     *                                      a single 'system.multicall' xml-rpc method call to forward to the server all
777 375
     *                                      the requests in a single HTTP round trip, unless $this->no_multicall has
778
     *                                      been previously set to TRUE (see the multicall method below), in which case
779 375
     *                                      many consecutive xml-rpc requests will be sent. The method will return an
780 2
     *                                      array of Response objects in both cases.
781
     *                                      The third variant allows to build by hand (or any other means) a complete
782
     *                                      xml-rpc request message, and send it to the server. $req should be a string
783 375
     *                                      containing the complete xml representation of the request. It is e.g. useful
784 375
     *                                      when, for maximal speed of execution, the request is serialized into a
785 32
     *                                      string using the native php xml-rpc functions (see http://www.php.net/xmlrpc)
786
     * @param integer $timeout deprecated. Connection timeout, in seconds, If unspecified, the timeout set with setOption
787
     *                         will be used. If that is 0, a platform specific timeout will apply.
788
     *                         This timeout value is passed to fsockopen(). It is also used for detecting server
789
     *                         timeouts during communication (i.e. if the server does not send anything to the client
790
     *                         for $timeout seconds, the connection will be closed).
791 32
     * @param string $method deprecated. Use the same value in the constructor instead.
792
     *                       Valid values are 'http', 'http11', 'https', 'h2' and 'h2c'. If left empty,
793
     *                       the http protocol chosen during creation of the object will be used.
794 32
     *                       Use 'h2' to make the lib attempt to use http/2 over a secure connection, and 'h2c'
795
     *                       for http/2 without tls. Note that 'h2c' will not use the h2c 'upgrade' method, and be
796
     *                       thus incompatible with any server/proxy not supporting http/2. This is because POST
797 32
     *                       request are not compatible with h2c upgrade.
798
     * @return Response|Response[] Note that the client will always return a Response object, even if the call fails
799
     *
800 32
     * @todo allow throwing exceptions instead of returning responses in case of failed calls and/or Fault responses
801 32
     * @todo refactor: we now support many options besides connection timeout and http version to use. Why only privilege those?
802
     */
803
    public function send($req, $timeout = 0, $method = '')
804 375
    {
805
        if ($method !== '' || $timeout !== 0) {
806 375
            $this->logDeprecation("Using non-default values for arguments 'method' and 'timeout' when calling method " . __METHOD__ . ' is deprecated');
807 64
        }
808
809 311
        // if user does not specify http protocol, use native method of this client
810
        // (i.e. method set during call to constructor)
811
        if ($method == '') {
812 375
            $method = $this->method;
813 375
        }
814
815 375
        if ($timeout == 0) {
816 375
            $timeout = $this->timeout;
817 375
        }
818 374
819 367
        if (is_array($req)) {
820
            // $req is an array of Requests
821
            /// @todo switch to the new syntax for multicall
822 1
            return $this->multicall($req, $timeout, $method);
823
        } elseif (is_string($req)) {
824
            $n = new static::$requestClass('');
825
            /// @todo we should somehow allow the caller to declare a custom contenttype too, esp. for the charset declaration
826
            $n->setPayload($req);
827 1
            $req = $n;
828 1
        }
829
830 1
        // where req is a Request
831
        $req->setDebug($this->debug);
832
833 374
        /// @todo we could be smarter about this and not force usage of curl for https if not present as well as use the
834
        ///       presence of curl_extra_opts or socket_extra_opts as a hint
835
        $useCurl = ($this->use_curl == self::USE_CURL_ALWAYS) || ($this->use_curl == self::USE_CURL_AUTO && (
836
            in_array($method, array('https', 'http11', 'h2c', 'h2')) ||
837
            ($this->username != '' && $this->authtype != 1) ||
838
            ($this->proxy != '' && $this->proxy_user != '' && $this->proxy_authtype != 1)
839
        ));
840
841
        // BC - we go through sendPayloadCURL/sendPayloadSocket in case some subclass reimplemented those
842
        if ($useCurl) {
843 374
            $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

843
            $r = /** @scrutinizer ignore-deprecated */ $this->sendPayloadCURL(
Loading history...
844
                $req,
845
                $this->server,
846
                $this->port,
847 374
                $timeout,
848 374
                $this->username,
849 374
                $this->password,
850
                $this->authtype,
851 374
                $this->cert,
852
                $this->certpass,
853 374
                $this->cacert,
854
                $this->cacertdir,
855
                $this->proxy,
856
                $this->proxyport,
857
                $this->proxy_user,
858
                $this->proxy_pass,
859
                $this->proxy_authtype,
860
                // BC
861
                $method == 'http11' ? 'http' : $method,
862
                $this->keepalive,
863
                $this->key,
864
                $this->keypass,
865
                $this->sslversion
866
            );
867
        } else {
868
            $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

868
            $r = /** @scrutinizer ignore-deprecated */ $this->sendPayloadSocket(
Loading history...
869
                $req,
870
                $this->server,
871
                $this->port,
872
                $timeout,
873
                $this->username,
874
                $this->password,
875
                $this->authtype,
876
                $this->cert,
877
                $this->certpass,
878
                $this->cacert,
879
                $this->cacertdir,
880
                $this->proxy,
881
                $this->proxyport,
882
                $this->proxy_user,
883
                $this->proxy_pass,
884
                $this->proxy_authtype,
885
                $method,
886 322
                $this->key,
887
                $this->keypass,
888
                $this->sslversion
889
            );
890
        }
891 322
892
        return $r;
893
    }
894
895 322
    /**
896
     * @param Request $req
897 96
     * @param string $method
898 96
     * @param string $server
899
     * @param int $port
900
     * @param string $path
901
     * @param array $opts
902
     * @return Response
903
     */
904 322
    protected function sendViaSocket($req, $method, $server, $port, $path, $opts)
905 322
    {
906
        /// @todo log a warning if passed an unsupported method
907
908
        // Only create the payload if it was not created previously
909
        /// @todo what if the request's payload was created with a different encoding?
910 322
        ///       Also, if we do not call serialize(), the request will not set its content-type to have the charset declared
911
        $payload = $req->getPayload();
912
        if (empty($payload)) {
913
            $payload = $req->serialize($opts['request_charset_encoding']);
914
        }
915 322
916
        // Deflate request body and set appropriate request headers
917 322
        $encodingHdr = '';
918
        if ($opts['request_compression'] == 'gzip' || $opts['request_compression'] == 'deflate') {
919
            if ($opts['request_compression'] == 'gzip' && function_exists('gzencode')) {
920
                $a = @gzencode($payload);
921
                if ($a) {
922
                    $payload = $a;
923
                    $encodingHdr = "Content-Encoding: gzip\r\n";
924
                } else {
925
                    $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzencode failure in compressing request');
926
                }
927
            } else if (function_exists('gzcompress')) {
928
                $a = @gzcompress($payload);
929 322
                if ($a) {
930
                    $payload = $a;
931
                    $encodingHdr = "Content-Encoding: deflate\r\n";
932 1
                } else {
933 1
                    $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzcompress failure in compressing request');
934 1
                }
935 1
            } else {
936 1
                $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported by this PHP install');
937
            }
938
        } else {
939 322
            if ($opts['request_compression'] != '') {
940 160
                $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported');
941
            }
942 322
        }
943
944 322
        // thanks to Grant Rauscher
945
        $credentials = '';
946
        if ($opts['username'] != '') {
947
            if ($opts['authtype'] != 1) {
948
                $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported with HTTP 1.0');
949
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
950 322
                    PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': only Basic auth is supported with HTTP 1.0');
951
            }
952
            $credentials = 'Authorization: Basic ' . base64_encode($opts['username'] . ':' . $opts['password']) . "\r\n";
953 323
        }
954
955
        $acceptedEncoding = '';
956
        if (is_array($opts['accepted_compression']) && count($opts['accepted_compression'])) {
957
            $acceptedEncoding = 'Accept-Encoding: ' . implode(', ', $opts['accepted_compression']) . "\r\n";
958 323
        }
959 322
960 226
        if ($port == 0) {
961
            $port = ($method === 'https') ? 443 : 80;
962 96
        }
963
964
        $proxyCredentials = '';
965
        if ($opts['proxy']) {
966
            if ($opts['proxyport'] == 0) {
967 323
                $opts['proxyport'] = 8080;
968 302
            }
969
            $connectServer = $opts['proxy'];
970
            $connectPort = $opts['proxyport'];
971
            $transport = 'tcp';
972 323
            /// @todo check: should we not use https in some cases?
973 323
            $uri = 'http://' . $server . ':' . $port . $path;
974 64
            if ($opts['proxy_user'] != '') {
975 32
                if ($opts['proxy_authtype'] != 1) {
976 32
                    $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported with HTTP 1.0');
977 32
                    return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
978 32
                        PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': only Basic auth to proxy is supported with HTTP 1.0');
979
                }
980
                $proxyCredentials = 'Proxy-Authorization: Basic ' . base64_encode($opts['proxy_user'] . ':' .
981 32
                    $opts['proxy_pass']) . "\r\n";
982 32
            }
983 32
        } else {
984 64
            $connectServer = $server;
985
            $connectPort = $port;
986
            $transport = ($method === 'https') ? 'tls' : 'tcp';
987
            $uri = $path;
988 259
        }
989
990
        // Cookie generation, as per RFC 6265
991 323
        // NB: the following code does not honour 'expires', 'path' and 'domain' cookie attributes set to client obj by the user...
992 323
        $cookieHeader = '';
993 64
        if (count($opts['cookies'])) {
994
            $version = '';
995 259
            foreach ($opts['cookies'] as $name => $cookie) {
996 32
                /// @todo should we sanitize the cookie value on behalf of the user? See setCookie comments
997
                $cookieHeader .= ' ' . $name . '=' . $cookie['value'] . ";";
998
            }
999 227
            $cookieHeader = 'Cookie:' . $version . substr($cookieHeader, 0, -1) . "\r\n";
1000
        }
1001
1002 323
        // omit port if default
1003 323
        if (($port == 80 && in_array($method, array('http', 'http10'))) || ($port == 443 && $method == 'https')) {
1004 322
            $port = '';
1005
        } else {
1006
            $port = ':' . $port;
1007 56
        }
1008
1009
        $op = 'POST ' . $uri . " HTTP/1.0\r\n" .
1010
            'User-Agent: ' . $opts['user_agent'] . "\r\n" .
1011 323
            'Host: ' . $server . $port . "\r\n" .
1012
            $credentials .
1013 323
            $proxyCredentials .
1014
            $acceptedEncoding .
1015
            $encodingHdr .
1016
            'Accept-Charset: ' . implode(',', $opts['accepted_charset_encodings']) . "\r\n" .
1017 323
            $cookieHeader .
1018
            'Content-Type: ' . $req->getContentType() . "\r\nContent-Length: " .
1019 323
            strlen($payload) . "\r\n\r\n" .
1020
            $payload;
1021 323
1022
        if ($opts['debug'] > 1) {
1023
            $this->getLogger()->debug("---SENDING---\n$op\n---END---");
1024 323
        }
1025
1026
        $contextOptions = array();
1027
        if ($method == 'https') {
1028 323
            if ($opts['cert'] != '') {
1029
                $contextOptions['ssl']['local_cert'] = $opts['cert'];
1030
                if ($opts['certpass'] != '') {
1031 66
                    $contextOptions['ssl']['passphrase'] = $opts['certpass'];
1032 64
                }
1033
            }
1034 2
            if ($opts['cacert'] != '') {
1035
                $contextOptions['ssl']['cafile'] = $opts['cacert'];
1036
            }
1037
            if ($opts['cacertdir'] != '') {
1038 323
                $contextOptions['ssl']['capath'] = $opts['cacertdir'];
1039
            }
1040 323
            if ($opts['key'] != '') {
1041 161
                $contextOptions['ssl']['local_pk'] = $opts['key'];
1042
            }
1043
            $contextOptions['ssl']['verify_peer'] = $opts['verifypeer'];
1044 323
            $contextOptions['ssl']['verify_peer_name'] = $opts['verifypeer'];
1045 64
1046
            if ($opts['sslversion'] != 0) {
1047
                /// @see https://www.php.net/manual/en/function.curl-setopt.php, https://www.php.net/manual/en/migration56.openssl.php
1048
                switch($opts['sslversion']) {
1049
                    /// @todo what does this map to? 1.0-1.3?
1050 323
                    //case 1: // TLSv1
1051
                    //    break;
1052 323
                    case 2: // SSLv2
1053
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_SSLv2_CLIENT;
1054 323
                        break;
1055 272
                    case 3: // SSLv3
1056
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_SSLv3_CLIENT;
1057
                        break;
1058 323
                    case 4: // TLSv1.0
1059 323
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT;
1060 32
                        break;
1061 32
                    case 5: // TLSv1.1
1062 291
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
1063
                        break;
1064
                    case 6: // TLSv1.2
1065 291
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
1066 32
                        break;
1067 32
                    case 7: // TLSv1.3
1068 259
                        if (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT')) {
1069 32
                            $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT;
1070 32
                        } else {
1071
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
1072
                                PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': TLS-1.3 only is supported with PHP 7.4 or later');
1073 323
                        }
1074 32
                        break;
1075 32
                    default:
1076 32
                        return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
1077
                            PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': Unsupported required TLS version');
1078
                }
1079
            }
1080
        }
1081
1082 323
        foreach ($opts['extracurlopts'] as $proto => $protoOpts) {
1083
            foreach ($protoOpts as $key => $val) {
1084 96
                $contextOptions[$proto][$key] = $val;
1085
            }
1086
        }
1087
1088 96
        $context = stream_context_create($contextOptions);
1089
1090
        if ($opts['timeout'] <= 0) {
1091
            $connectTimeout = ini_get('default_socket_timeout');
1092 96
        } else {
1093
            $connectTimeout = $opts['timeout'];
1094 96
        }
1095
1096
        $this->errno = 0;
1097 96
        $this->errstr = '';
1098
1099
        $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr, $connectTimeout,
1100
            STREAM_CLIENT_CONNECT, $context);
1101 96
        if ($fp) {
0 ignored issues
show
introduced by
$fp is of type false|resource, thus it always evaluated to false.
Loading history...
1102
            if ($opts['timeout'] > 0) {
1103
                stream_set_timeout($fp, $opts['timeout'], 0);
1104
            }
1105 96
        } else {
1106
            if ($this->errstr == '') {
1107
                $err = error_get_last();
1108
                $this->errstr = $err['message'];
1109
            }
1110 96
1111
            $this->errstr = 'Connect error: ' . $this->errstr;
1112 96
            $r = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr . ' (' . $this->errno . ')');
1113
1114
            return $r;
1115
        }
1116 323
1117 64
        if (!fputs($fp, $op, strlen($op))) {
1118
            fclose($fp);
1119
            $this->errstr = 'Write error';
1120 64
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr);
1121 64
        }
1122
1123
        // Close socket before parsing.
1124
        // It should yield slightly better execution times, and make easier recursive calls (e.g. to follow http redirects)
1125
        $ipd = '';
1126
        do {
1127
            // shall we check for $data === FALSE?
1128
            // as per the manual, it signals an error
1129
            $ipd .= fread($fp, 32768);
1130
        } while (!feof($fp));
1131
        fclose($fp);
1132
1133 323
        return $req->parseResponse($ipd, false, $opts['return_type']);
1134 271
    }
1135 271
1136 271
    /**
1137
     * Contributed by Justin Miller
1138 271
     * Requires curl to be built into PHP
1139
     * NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers!
1140
     *
1141 323
     * @param Request $req
1142
     * @param string $method
1143
     * @param string $server
1144
     * @param int $port
1145 323
     * @param string $path
1146
     * @param array $opts the keys/values match self::getOptions
1147
     * @return Response
1148
     *
1149 323
     * @todo the $path arg atm is ignored. What to do if it is != $this->path?
1150
     */
1151
    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

1151
    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...
1152
    {
1153
        if (!function_exists('curl_init')) {
1154
            $this->errstr = 'CURL unavailable on this install';
1155
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_curl'], PhpXmlRpc::$xmlrpcstr['no_curl']);
1156
        }
1157
        if ($method == 'https' || $method == 'h2') {
1158
            // q: what about installs where we get back a string, but curl is linked to other ssl libs than openssl?
1159
            if (($info = curl_version()) &&
1160
                ((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...
1161
            ) {
1162
                $this->errstr = 'SSL unavailable on this install';
1163
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_ssl'], PhpXmlRpc::$xmlrpcstr['no_ssl']);
1164
            }
1165
        }
1166
        if (($method == 'h2' && !defined('CURL_HTTP_VERSION_2_0')) ||
1167
            ($method == 'h2c' && !defined('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE'))) {
1168
            $this->errstr = 'HTTP/2 unavailable on this install';
1169
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_http2'], PhpXmlRpc::$xmlrpcstr['no_http2']);
1170
        }
1171
1172
        // BC - we go through prepareCurlHandle in case some subclass reimplemented it
1173
        $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

1173
        $curl = /** @scrutinizer ignore-deprecated */ $this->prepareCurlHandle($req, $server, $port, $opts['timeout'], $opts['username'], $opts['password'],
Loading history...
1174 66
            $opts['authtype'], $opts['cert'], $opts['certpass'], $opts['cacert'], $opts['cacertdir'], $opts['proxy'],
1175
            $opts['proxyport'], $opts['proxy_user'], $opts['proxy_pass'], $opts['proxy_authtype'], $method,
1176 66
            $opts['keepalive'], $opts['key'], $opts['keypass'], $opts['sslversion']);
1177
1178
        if (!$curl) {
1179 66
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] .
1180 45
                ': error during curl initialization. Check php error log for details');
1181 45
        }
1182
1183 45
        $result = curl_exec($curl);
1184
1185
        if ($opts['debug'] > 1) {
1186
            $message = "---CURL INFO---\n";
1187
            foreach (curl_getinfo($curl) as $name => $val) {
1188
                if (is_array($val)) {
1189
                    $val = implode("\n", $val);
1190
                }
1191
                $message .= $name . ': ' . $val . "\n";
1192
            }
1193
            $message .= '---END---';
1194
            $this->getLogger()->debug($message);
1195
        }
1196
1197
        if (!$result) {
1198
            /// @todo we should use a better check here - what if we get back '' or '0'?
1199
1200
            $this->errstr = 'no response';
1201 23
            $resp = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] .
1202
                ': ' . curl_error($curl));
1203
            curl_close($curl);
1204 23
            if ($opts['keepalive']) {
1205 23
                $this->xmlrpc_curl_handle = null;
1206
            }
1207
        } else {
1208
            if (!$opts['keepalive']) {
1209 23
                curl_close($curl);
1210 23
            }
1211
            $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

1211
            $resp = $req->parseResponse(/** @scrutinizer ignore-type */ $result, true, $opts['return_type']);
Loading history...
1212
            if ($opts['keepalive']) {
1213
                /// @todo if we got back a 302 or 308, we should not reuse the curl handle for later calls
1214
                if ($resp->faultCode() == PhpXmlRpc::$xmlrpcerr['http_error']) {
1215
                    curl_close($curl);
1216
                    $this->xmlrpc_curl_handle = null;
1217
                }
1218
            }
1219
        }
1220
1221 23
        return $resp;
1222
    }
1223
1224
    /**
1225
     * @param Request $req
1226
     * @param string $method
1227
     * @param string $server
1228
     * @param int $port
1229
     * @param string $path
1230
     * @param array $opts the keys/values match self::getOptions
1231
     * @return \CurlHandle|resource|false
1232
     *
1233
     * @todo allow this method to either throw or return a Response, so that we can pass back to caller more info on errors
1234
     */
1235 45
    protected function createCURLHandle($req, $method, $server, $port, $path, $opts)
1236
    {
1237
        if ($port == 0) {
1238 45
            if (in_array($method, array('http', 'http10', 'http11', 'h2c'))) {
1239 45
                $port = 80;
1240 45
            } else {
1241 45
                $port = 443;
1242 45
            }
1243 45
        }
1244 45
1245
        // Only create the payload if it was not created previously
1246 45
        $payload = $req->getPayload();
1247 45
        if (empty($payload)) {
1248
            $payload = $req->serialize($opts['request_charset_encoding']);
1249 45
        }
1250 45
1251
        // Deflate request body and set appropriate request headers
1252
        $encodingHdr = '';
1253 45
        if (($opts['request_compression'] == 'gzip' || $opts['request_compression'] == 'deflate')) {
1254
            if ($opts['request_compression'] == 'gzip' && function_exists('gzencode')) {
1255 45
                $a = @gzencode($payload);
1256
                if ($a) {
1257
                    $payload = $a;
1258
                    $encodingHdr = 'Content-Encoding: gzip';
1259
                } else {
1260
                    $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzencode failure in compressing request');
1261 45
                }
1262
            } else if (function_exists('gzcompress')) {
1263 45
                $a = @gzcompress($payload);
1264
                if ($a) {
1265 45
                    $payload = $a;
1266
                    $encodingHdr = 'Content-Encoding: deflate';
1267 22
                } else {
1268 22
                    $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzcompress failure in compressing request');
1269
                }
1270
            } else {
1271 22
                $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported by this PHP install');
1272 22
            }
1273
        } else {
1274
            if ($opts['request_compression'] != '') {
1275
                $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported');
1276 22
            }
1277 22
        }
1278 22
1279 22
        if (!$opts['keepalive'] || !$this->xmlrpc_curl_handle) {
1280
            if ($method == 'http11' || $method == 'http10' || $method == 'h2c') {
1281
                $protocol = 'http';
1282 22
            } else {
1283 22
                if ($method == 'h2') {
1284 22
                    $protocol = 'https';
1285
                } else {
1286
                    // http, https
1287
                    $protocol = $method;
1288 22
                    if (strpos($protocol, ':') !== false) {
1289 22
                        $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ": warning - attempted hacking attempt?. The curl protocol requested for the call is: '$protocol'");
1290 22
                        return false;
1291
                    }
1292 22
                }
1293 22
            }
1294
            $curl = curl_init($protocol . '://' . $server . ':' . $port . $path);
1295
            if (!$curl) {
1296 22
                return false;
1297 22
            }
1298
            if ($opts['keepalive']) {
1299
                $this->xmlrpc_curl_handle = $curl;
1300 22
            }
1301 22
        } else {
1302
            $curl = $this->xmlrpc_curl_handle;
1303
        }
1304
1305
        // results into variable
1306
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
1307 22
1308
        if ($opts['debug'] > 1) {
1309
            curl_setopt($curl, CURLOPT_VERBOSE, true);
1310
            /// @todo redirect curlopt_stderr to some stream which can be piped to the logger
1311 24
        }
1312 24
        curl_setopt($curl, CURLOPT_USERAGENT, $opts['user_agent']);
1313
        // required for XMLRPC: post the data
1314
        curl_setopt($curl, CURLOPT_POST, 1);
1315 24
        // the data
1316 24
        curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
1317
1318
        // return the header too
1319
        curl_setopt($curl, CURLOPT_HEADER, 1);
1320 24
1321 24
        // NB: if we set an empty string, CURL will add http header indicating
1322 24
        // ALL methods it is supporting. This is possibly a better option than letting the user tell what curl can / cannot do...
1323 24
        if (is_array($opts['accepted_compression']) && count($opts['accepted_compression'])) {
1324 24
            //curl_setopt($curl, CURLOPT_ENCODING, implode(',', $opts['accepted_compression']));
1325
            // empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1326
            if (count($opts['accepted_compression']) == 1) {
1327
                curl_setopt($curl, CURLOPT_ENCODING, $opts['accepted_compression'][0]);
1328 24
            } else {
1329 24
                curl_setopt($curl, CURLOPT_ENCODING, '');
1330 22
            }
1331 22
        }
1332
        // extra headers
1333 22
        $headers = array('Content-Type: ' . $req->getContentType(), 'Accept-Charset: ' . implode(',', $opts['accepted_charset_encodings']));
1334
        // if no keepalive is wanted, let the server know it in advance
1335
        if (!$opts['keepalive']) {
1336 22
            $headers[] = 'Connection: close';
1337
        }
1338 22
        // request compression header
1339
        if ($encodingHdr) {
1340
            $headers[] = $encodingHdr;
1341 22
        }
1342 22
1343
        // Fix the HTTP/1.1 417 Expectation Failed Bug (curl by default adds a 'Expect: 100-continue' header when POST
1344
        // size exceeds 1025 bytes, apparently)
1345
        $headers[] = 'Expect:';
1346
1347
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
1348 24
        // timeout is borked
1349
        if ($opts['timeout']) {
1350
            curl_setopt($curl, CURLOPT_TIMEOUT, $opts['timeout'] == 1 ? 1 : $opts['timeout'] - 1);
1351
        }
1352
1353
        switch ($method) {
1354
            case 'http10':
1355
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
1356
                break;
1357
            case 'http11':
1358
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
1359
                break;
1360
            case 'h2c':
1361
                if (defined('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE')) {
1362
                    curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE);
1363
                } else {
1364
                    $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. HTTP2 is not supported by the current PHP/curl install');
1365
                    curl_close($curl);
1366
                    return false;
1367
                }
1368
                break;
1369
            case 'h2':
1370
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
1371
                break;
1372
        }
1373
1374
        if ($opts['username'] && $opts['password']) {
1375
            curl_setopt($curl, CURLOPT_USERPWD, $opts['username'] . ':' . $opts['password']);
1376
            if (defined('CURLOPT_HTTPAUTH')) {
1377
                curl_setopt($curl, CURLOPT_HTTPAUTH, $opts['authtype']);
1378
            } elseif ($opts['authtype'] != 1) {
1379
                $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported by the current PHP/curl install');
1380
                curl_close($curl);
1381
                return false;
1382
            }
1383
        }
1384
1385
        // note: h2c is http2 without the https. No need to have it in this IF
1386
        if ($method == 'https' || $method == 'h2') {
1387
            // set cert file
1388
            if ($opts['cert']) {
1389
                curl_setopt($curl, CURLOPT_SSLCERT, $opts['cert']);
1390
            }
1391
            // set cert password
1392
            if ($opts['certpass']) {
1393
                curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $opts['certpass']);
1394
            }
1395
            // whether to verify remote host's cert
1396
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $opts['verifypeer']);
1397
            // set ca certificates file/dir
1398
            if ($opts['cacert']) {
1399
                curl_setopt($curl, CURLOPT_CAINFO, $opts['cacert']);
1400
            }
1401
            if ($opts['cacertdir']) {
1402
                curl_setopt($curl, CURLOPT_CAPATH, $opts['cacertdir']);
1403
            }
1404
            // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1405
            if ($opts['key']) {
1406
                curl_setopt($curl, CURLOPT_SSLKEY, $opts['key']);
1407
            }
1408
            // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1409
            if ($opts['keypass']) {
1410
                curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $opts['keypass']);
1411
            }
1412
            // whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that
1413
            // it matches the hostname used
1414
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $opts['verifyhost']);
1415
            // allow usage of different SSL versions
1416
            curl_setopt($curl, CURLOPT_SSLVERSION, $opts['sslversion']);
1417
        }
1418
1419
        // proxy info
1420
        if ($opts['proxy']) {
1421
            if ($opts['proxyport'] == 0) {
1422
                $opts['proxyport'] = 8080; // NB: even for HTTPS, local connection is on port 8080
1423
            }
1424
            curl_setopt($curl, CURLOPT_PROXY, $opts['proxy'] . ':' . $opts['proxyport']);
1425
            if ($opts['proxy_user']) {
1426
                curl_setopt($curl, CURLOPT_PROXYUSERPWD, $opts['proxy_user'] . ':' . $opts['proxy_pass']);
1427
                if (defined('CURLOPT_PROXYAUTH')) {
1428
                    curl_setopt($curl, CURLOPT_PROXYAUTH, $opts['proxy_authtype']);
1429
                } elseif ($opts['proxy_authtype'] != 1) {
1430
                    $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported by the current PHP/curl install');
1431
                    curl_close($curl);
1432
                    return false;
1433
                }
1434
            }
1435
        }
1436
1437
        // NB: should we build cookie http headers by hand rather than let CURL do it?
1438
        // NB: the following code does not honour 'expires', 'path' and 'domain' cookie attributes set to client obj by the user...
1439
        if (count($opts['cookies'])) {
1440
            $cookieHeader = '';
1441
            foreach ($opts['cookies'] as $name => $cookie) {
1442
                $cookieHeader .= $name . '=' . $cookie['value'] . '; ';
1443
            }
1444
            curl_setopt($curl, CURLOPT_COOKIE, substr($cookieHeader, 0, -2));
1445
        }
1446
1447
        foreach ($opts['extracurlopts'] as $opt => $val) {
1448
            curl_setopt($curl, $opt, $val);
1449
        }
1450
1451
        if ($opts['debug'] > 1) {
1452
            $this->getLogger()->debug("---SENDING---\n$payload\n---END---");
1453
        }
1454
1455
        return $curl;
1456
    }
1457
1458
    /**
1459
     * Send an array of requests and return an array of responses.
1460
     *
1461
     * Unless $this->no_multicall has been set to true, it will try first to use one single xml-rpc call to server method
1462
     * system.multicall, and revert to sending many successive calls in case of failure.
1463
     * This failure is also stored in $this->no_multicall for subsequent calls.
1464
     * Unfortunately, there is no server error code universally used to denote the fact that multicall is unsupported,
1465
     * so there is no way to reliably distinguish between that and a temporary failure.
1466
     * If you are sure that server supports multicall and do not want to fallback to using many single calls, set the
1467
     * 2np parameter to FALSE.
1468
     *
1469
     * NB: trying to shoehorn extra functionality into existing syntax has resulted
1470
     * in pretty much convoluted code...
1471
     *
1472
     * @param Request[] $reqs an array of Request objects
1473
     * @param bool $noFallback When true, upon receiving an error during multicall, multiple single calls will not be
1474
     *                         attempted.
1475
     *                         Deprecated alternative, was: int - "connection timeout (in seconds). See the details in the
1476
     *                         docs for the send() method". Please use setOption instead to set a timeout
1477
     * @param string $method deprecated. Was: "the http protocol variant to be used. See the details in the docs for the send() method."
1478
     *                       Please use the constructor to set an http protocol variant.
1479
     * @param boolean $fallback deprecated. Was: "w"hen true, upon receiving an error during multicall, multiple single
1480
     *                          calls will be attempted"
1481
     * @return Response[]
1482
     */
1483
    public function multicall($reqs, $timeout = 0, $method = '', $fallback = true)
1484
    {
1485
        // BC
1486
        if (is_bool($timeout) && $fallback === true) {
1487
            $fallback = !$timeout;
1488
            $timeout = 0;
1489
        }
1490
1491
        if ($method == '') {
1492
            $method = $this->method;
1493
        }
1494
1495
        if (!$this->no_multicall) {
1496
            $results = $this->_try_multicall($reqs, $timeout, $method);
1497
            /// @todo how to handle the case of $this->return_type = xml?
1498
            if (is_array($results)) {
1499
                // System.multicall succeeded
1500
                return $results;
1501
            } else {
1502
                // either system.multicall is unsupported by server, or the call failed for some other reason.
1503
                // Feature creep: is there a way to tell apart unsupported multicall from other faults?
1504
                if ($fallback) {
1505
                    // Don't try it next time...
1506
                    $this->no_multicall = true;
1507
                } else {
1508
                    $result = $results;
1509
                }
1510
            }
1511
        } else {
1512
            // override fallback, in case careless user tries to do two
1513
            // opposite things at the same time
1514
            $fallback = true;
1515
        }
1516
1517
        $results = array();
1518
        if ($fallback) {
1519
            // system.multicall is (probably) unsupported by server: emulate multicall via multiple requests
1520
            /// @todo use curl multi_ functions to make this quicker (see the implementation in the parallel.php demo)
1521
            foreach ($reqs as $req) {
1522
                $results[] = $this->send($req, $timeout, $method);
1523
            }
1524
        } else {
1525
            // user does NOT want to fallback on many single calls: since we should always return an array of responses,
1526
            // we return an array with the same error repeated n times
1527
            foreach ($reqs as $req) {
1528
                $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...
1529
            }
1530
        }
1531
1532
        return $results;
1533
    }
1534
1535
    /**
1536
     * Attempt to boxcar $reqs via system.multicall.
1537
     *
1538
     * @param Request[] $reqs
1539
     * @param int $timeout
1540
     * @param string $method
1541
     * @return Response[]|Response a single Response when the call returned a fault / does not conform to what we expect
1542
     *                             from a multicall response
1543
     */
1544
    private function _try_multicall($reqs, $timeout, $method)
1545
    {
1546
        // Construct multicall request
1547
        $calls = array();
1548
        foreach ($reqs as $req) {
1549
            $call['methodName'] = new Value($req->method(), 'string');
1550
            $numParams = $req->getNumParams();
1551
            $params = array();
1552
            for ($i = 0; $i < $numParams; $i++) {
1553
                $params[$i] = $req->getParam($i);
1554
            }
1555
            $call['params'] = new Value($params, 'array');
1556
            $calls[] = new Value($call, 'struct');
1557
        }
1558
        $multiCall = new static::$requestClass('system.multicall');
1559
        $multiCall->addParam(new Value($calls, 'array'));
1560
1561
        // Attempt RPC call
1562
        $result = $this->send($multiCall, $timeout, $method);
1563
1564
        if ($result->faultCode() != 0) {
1565
            // call to system.multicall failed
1566
            return $result;
1567
        }
1568
1569
        // Unpack responses.
1570
        $rets = $result->value();
1571
        $response = array();
1572
1573
        if ($this->return_type == 'xml') {
1574
            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...
1575
                $response[] = new static::$responseClass($rets, 0, '', 'xml', $result->httpResponse());
1576
            }
1577
1578
        } elseif ($this->return_type == 'phpvals') {
1579
            if (!is_array($rets)) {
1580
                // bad return type from system.multicall
1581
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1582
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': not an array', 'phpvals', $result->httpResponse());
1583
            }
1584
            $numRets = count($rets);
1585
            if ($numRets != count($reqs)) {
1586
                // wrong number of return values.
1587
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1588
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': incorrect number of responses', 'phpvals',
1589
                    $result->httpResponse());
1590
            }
1591
1592
            for ($i = 0; $i < $numRets; $i++) {
1593
                $val = $rets[$i];
1594
                if (!is_array($val)) {
1595
                    return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1596
                        PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array or struct",
1597
                        'phpvals', $result->httpResponse());
1598
                }
1599
                switch (count($val)) {
1600
                    case 1:
1601
                        if (!isset($val[0])) {
1602
                            // Bad value
1603
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1604
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has no value",
1605
                                'phpvals', $result->httpResponse());
1606
                        }
1607
                        // Normal return value
1608
                        $response[$i] = new static::$responseClass($val[0], 0, '', 'phpvals', $result->httpResponse());
1609
                        break;
1610
                    case 2:
1611
                        /// @todo remove usage of @: it is apparently quite slow
1612
                        $code = @$val['faultCode'];
1613
                        if (!is_int($code)) {
1614
                            /// @todo should we check that it is != 0?
1615
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1616
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1617
                                'phpvals', $result->httpResponse());
1618
                        }
1619
                        $str = @$val['faultString'];
1620
                        if (!is_string($str)) {
1621
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1622
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no FaultString",
1623
                                'phpvals', $result->httpResponse());
1624
                        }
1625
                        $response[$i] = new static::$responseClass(0, $code, $str, 'phpvals', $result->httpResponse());
1626
                        break;
1627
                    default:
1628
                        return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1629
                            PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1630
                            'phpvals', $result->httpResponse());
1631
                }
1632
            }
1633
1634
        } else {
1635
            // return type == 'xmlrpcvals'
1636
            if ($rets->kindOf() != 'array') {
1637
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1638
                    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 1548. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
1639
                    $result->httpResponse());
1640
            }
1641
            $numRets = $rets->count();
1642
            if ($numRets != count($reqs)) {
1643
                // wrong number of return values.
1644
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1645
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': incorrect number of responses', 'xmlrpcvals',
1646
                    $result->httpResponse());
1647
            }
1648
1649
            foreach ($rets as $i => $val) {
1650
                switch ($val->kindOf()) {
1651
                    case 'array':
1652
                        if ($val->count() != 1) {
1653
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1654
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1655
                                'phpvals', $result->httpResponse());
1656
                        }
1657
                        // Normal return value
1658
                        $response[] = new static::$responseClass($val[0], 0, '', 'xmlrpcvals', $result->httpResponse());
1659
                        break;
1660
                    case 'struct':
1661
                        if ($val->count() != 2) {
1662
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1663
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1664
                                'phpvals', $result->httpResponse());
1665
                        }
1666
                        /** @var Value $code */
1667
                        $code = $val['faultCode'];
1668
                        if ($code->kindOf() != 'scalar' || $code->scalarTyp() != 'int') {
1669
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1670
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1671
                                'xmlrpcvals', $result->httpResponse());
1672
                        }
1673
                        /** @var Value $str */
1674
                        $str = $val['faultString'];
1675
                        if ($str->kindOf() != 'scalar' || $str->scalarTyp() != 'string') {
1676
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1677
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1678
                                'xmlrpcvals', $result->httpResponse());
1679
                        }
1680
                        $response[] = new static::$responseClass(0, $code->scalarVal(), $str->scalarVal(), 'xmlrpcvals', $result->httpResponse());
1681
                        break;
1682
                    default:
1683
                        return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1684
                            PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array or struct",
1685
                            'xmlrpcvals', $result->httpResponse());
1686
                }
1687
            }
1688
        }
1689
1690
        return $response;
1691
    }
1692
1693
    // *** BC layer ***
1694
1695
    /**
1696
     * @deprecated
1697
     *
1698
     * @param Request $req
1699
     * @param string $server
1700
     * @param int $port
1701
     * @param int $timeout
1702
     * @param string $username
1703
     * @param string $password
1704
     * @param int $authType
1705
     * @param string $proxyHost
1706
     * @param int $proxyPort
1707
     * @param string $proxyUsername
1708
     * @param string $proxyPassword
1709
     * @param int $proxyAuthType
1710
     * @param string $method
1711
     * @return Response
1712
     */
1713
    protected function sendPayloadHTTP10($req, $server, $port, $timeout = 0, $username = '', $password = '',
1714
        $authType = 1, $proxyHost = '', $proxyPort = 0, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1,
1715
        $method = 'http')
1716
    {
1717
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
1718
1719
        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

1719
        return /** @scrutinizer ignore-deprecated */ $this->sendPayloadSocket($req, $server, $port, $timeout, $username, $password, $authType, null, null,
Loading history...
1720
            null, null, $proxyHost, $proxyPort, $proxyUsername, $proxyPassword, $proxyAuthType, $method);
1721
    }
1722
1723
    /**
1724
     * @deprecated
1725
     *
1726
     * @param Request $req
1727
     * @param string $server
1728
     * @param int $port
1729
     * @param int $timeout
1730
     * @param string $username
1731
     * @param string $password
1732
     * @param int $authType
1733
     * @param string $cert
1734
     * @param string $certPass
1735
     * @param string $caCert
1736
     * @param string $caCertDir
1737
     * @param string $proxyHost
1738
     * @param int $proxyPort
1739
     * @param string $proxyUsername
1740
     * @param string $proxyPassword
1741
     * @param int $proxyAuthType
1742
     * @param bool $keepAlive
1743
     * @param string $key
1744
     * @param string $keyPass
1745
     * @param int $sslVersion
1746
     * @return Response
1747
     */
1748
    protected function sendPayloadHTTPS($req, $server, $port, $timeout = 0, $username = '', $password = '',
1749
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
1750
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $keepAlive = false, $key = '', $keyPass = '',
1751
        $sslVersion = 0)
1752
    {
1753
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
1754
1755
        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

1755
        return /** @scrutinizer ignore-deprecated */ $this->sendPayloadCURL($req, $server, $port, $timeout, $username,
Loading history...
1756
            $password, $authType, $cert, $certPass, $caCert, $caCertDir, $proxyHost, $proxyPort,
1757
            $proxyUsername, $proxyPassword, $proxyAuthType, 'https', $keepAlive, $key, $keyPass, $sslVersion);
1758
    }
1759
1760
    /**
1761
     * @deprecated
1762
     *
1763
     * @param Request $req
1764
     * @param string $server
1765
     * @param int $port
1766
     * @param int $timeout
1767
     * @param string $username
1768
     * @param string $password
1769
     * @param int $authType only value supported is 1
1770
     * @param string $cert
1771
     * @param string $certPass
1772
     * @param string $caCert
1773
     * @param string $caCertDir
1774
     * @param string $proxyHost
1775
     * @param int $proxyPort
1776
     * @param string $proxyUsername
1777
     * @param string $proxyPassword
1778
     * @param int $proxyAuthType only value supported is 1
1779
     * @param string $method 'http' (synonym for 'http10'), 'http10' or 'https'
1780
     * @param string $key
1781
     * @param string $keyPass @todo not implemented yet.
1782
     * @param int $sslVersion @todo not implemented yet. See http://php.net/manual/en/migration56.openssl.php
1783
     * @return Response
1784
     */
1785
    protected function sendPayloadSocket($req, $server, $port, $timeout = 0, $username = '', $password = '',
1786
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
1787
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'http', $key = '', $keyPass = '',
1788
        $sslVersion = 0)
1789
    {
1790
        $this->logDeprecationUnlessCalledBy('send');
1791
1792
        return $this->sendViaSocket($req, $method, $server, $port, $this->path, array(
1793
            'accepted_charset_encodings' => $this->accepted_charset_encodings,
1794
            'accepted_compression' => $this->accepted_compression,
1795
            'authtype' => $authType,
1796
            'cacert' => $caCert,
1797
            'cacertdir' => $caCertDir,
1798
            'cert' => $cert,
1799
            'certpass' => $certPass,
1800
            'cookies' => $this->cookies,
1801
            'debug' => $this->debug,
1802
            'extracurlopts' => $this->extracurlopts,
1803
            'extrasockopts' => $this->extrasockopts,
1804
            'keepalive' => $this->keepalive,
1805
            'key' => $key,
1806
            'keypass' => $keyPass,
1807
            'no_multicall' => $this->no_multicall,
1808
            'password' => $password,
1809
            'proxy' => $proxyHost,
1810
            'proxy_authtype' => $proxyAuthType,
1811
            'proxy_pass' => $proxyPassword,
1812
            'proxyport' => $proxyPort,
1813
            'proxy_user' => $proxyUsername,
1814
            'request_charset_encoding' => $this->request_charset_encoding,
1815
            'request_compression' => $this->request_compression,
1816
            'return_type' => $this->return_type,
1817
            'sslversion' => $sslVersion,
1818
            'timeout' => $timeout,
1819
            'username' => $username,
1820
            'user_agent' => $this->user_agent,
1821
            'use_curl' => $this->use_curl,
1822
            'verifyhost' => $this->verifyhost,
1823
            'verifypeer' => $this->verifypeer,
1824
        ));
1825
    }
1826
1827
    /**
1828
     * @deprecated
1829
     *
1830
     * @param Request $req
1831
     * @param string $server
1832
     * @param int $port
1833
     * @param int $timeout
1834
     * @param string $username
1835
     * @param string $password
1836
     * @param int $authType
1837
     * @param string $cert
1838
     * @param string $certPass
1839
     * @param string $caCert
1840
     * @param string $caCertDir
1841
     * @param string $proxyHost
1842
     * @param int $proxyPort
1843
     * @param string $proxyUsername
1844
     * @param string $proxyPassword
1845
     * @param int $proxyAuthType
1846
     * @param string $method 'http' (let curl decide), 'http10', 'http11', 'https', 'h2c' or 'h2'
1847
     * @param bool $keepAlive
1848
     * @param string $key
1849
     * @param string $keyPass
1850
     * @param int $sslVersion
1851
     * @return Response
1852
     */
1853
    protected function sendPayloadCURL($req, $server, $port, $timeout = 0, $username = '', $password = '',
1854
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
1855
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'https', $keepAlive = false, $key = '',
1856
        $keyPass = '', $sslVersion = 0)
1857
    {
1858
        $this->logDeprecationUnlessCalledBy('send');
1859
1860
        return $this->sendViaCURL($req, $method, $server, $port, $this->path, array(
1861
            'accepted_charset_encodings' => $this->accepted_charset_encodings,
1862
            'accepted_compression' => $this->accepted_compression,
1863
            'authtype' => $authType,
1864
            'cacert' => $caCert,
1865
            'cacertdir' => $caCertDir,
1866
            'cert' => $cert,
1867
            'certpass' => $certPass,
1868
            'cookies' => $this->cookies,
1869
            'debug' => $this->debug,
1870
            'extracurlopts' => $this->extracurlopts,
1871
            'extrasockopts' => $this->extrasockopts,
1872
            'keepalive' => $keepAlive,
1873
            'key' => $key,
1874
            'keypass' => $keyPass,
1875
            'no_multicall' => $this->no_multicall,
1876
            'password' => $password,
1877
            'proxy' => $proxyHost,
1878
            'proxy_authtype' => $proxyAuthType,
1879
            'proxy_pass' => $proxyPassword,
1880
            'proxyport' => $proxyPort,
1881
            'proxy_user' => $proxyUsername,
1882
            'request_charset_encoding' => $this->request_charset_encoding,
1883
            'request_compression' => $this->request_compression,
1884
            'return_type' => $this->return_type,
1885
            'sslversion' => $sslVersion,
1886
            'timeout' => $timeout,
1887
            'username' => $username,
1888
            'user_agent' => $this->user_agent,
1889
            'use_curl' => $this->use_curl,
1890
            'verifyhost' => $this->verifyhost,
1891
            'verifypeer' => $this->verifypeer,
1892
        ));
1893
    }
1894
1895
    /**
1896
     * @deprecated
1897
     *
1898
     * @param $req
1899
     * @param $server
1900
     * @param $port
1901
     * @param $timeout
1902
     * @param $username
1903
     * @param $password
1904
     * @param $authType
1905
     * @param $cert
1906
     * @param $certPass
1907
     * @param $caCert
1908
     * @param $caCertDir
1909
     * @param $proxyHost
1910
     * @param $proxyPort
1911
     * @param $proxyUsername
1912
     * @param $proxyPassword
1913
     * @param $proxyAuthType
1914
     * @param $method
1915
     * @param $keepAlive
1916
     * @param $key
1917
     * @param $keyPass
1918
     * @param $sslVersion
1919
     * @return false|\CurlHandle|resource
1920
     */
1921
    protected function prepareCurlHandle($req, $server, $port, $timeout = 0, $username = '', $password = '',
1922
         $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
1923
         $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'https', $keepAlive = false, $key = '',
1924
         $keyPass = '', $sslVersion = 0)
1925
    {
1926
        $this->logDeprecationUnlessCalledBy('sendViaCURL');
1927
1928
        return $this->createCURLHandle($req, $method, $server, $port, $this->path, array(
1929
            'accepted_charset_encodings' => $this->accepted_charset_encodings,
1930
            'accepted_compression' => $this->accepted_compression,
1931
            'authtype' => $authType,
1932
            'cacert' => $caCert,
1933
            'cacertdir' => $caCertDir,
1934
            'cert' => $cert,
1935
            'certpass' => $certPass,
1936
            'cookies' => $this->cookies,
1937
            'debug' => $this->debug,
1938
            'extracurlopts' => $this->extracurlopts,
1939
            'keepalive' => $keepAlive,
1940
            'key' => $key,
1941
            'keypass' => $keyPass,
1942
            'no_multicall' => $this->no_multicall,
1943
            'password' => $password,
1944
            'proxy' => $proxyHost,
1945
            'proxy_authtype' => $proxyAuthType,
1946
            'proxy_pass' => $proxyPassword,
1947
            'proxyport' => $proxyPort,
1948
            'proxy_user' => $proxyUsername,
1949
            'request_charset_encoding' => $this->request_charset_encoding,
1950
            'request_compression' => $this->request_compression,
1951
            'return_type' => $this->return_type,
1952
            'sslversion' => $sslVersion,
1953
            'timeout' => $timeout,
1954
            'username' => $username,
1955
            'user_agent' => $this->user_agent,
1956
            'use_curl' => $this->use_curl,
1957
            'verifyhost' => $this->verifyhost,
1958
            'verifypeer' => $this->verifypeer,
1959
        ));
1960
    }
1961
1962
    // we have to make this return by ref in order to allow calls such as `$resp->_cookies['name'] = ['value' => 'something'];`
1963
    public function &__get($name)
1964
    {
1965
        if (in_array($name, static::$options)) {
1966
            $this->logDeprecation('Getting property Client::' . $name . ' is deprecated');
1967
            return $this->$name;
1968
        }
1969
1970
        switch ($name) {
1971
            case 'errno':
1972
            case 'errstr':
1973
            case 'method':
1974
            case 'server':
1975
            case 'port':
1976
            case 'path':
1977
                $this->logDeprecation('Getting property Client::' . $name . ' is deprecated');
1978
                return $this->$name;
1979
            default:
1980
                /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout...
1981
                $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
1982
                trigger_error('Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
1983
                $result = null;
1984
                return $result;
1985
        }
1986
    }
1987
1988
    public function __set($name, $value)
1989
    {
1990
        if (in_array($name, static::$options)) {
1991
            $this->logDeprecation('Setting property Client::' . $name . ' is deprecated');
1992
            $this->$name = $value;
1993
            return;
1994
        }
1995
1996
        switch ($name) {
1997
            case 'errno':
1998
            case 'errstr':
1999
            case 'method':
2000
            case 'server':
2001
            case 'port':
2002
            case 'path':
2003
                $this->logDeprecation('Setting property Client::' . $name . ' is deprecated');
2004
                $this->$name = $value;
2005
                return;
2006
            default:
2007
                /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout...
2008
                $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
2009
                trigger_error('Undefined property via __set(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
2010
        }
2011
    }
2012
2013
    public function __isset($name)
2014
    {
2015
        if (in_array($name, static::$options)) {
2016
            $this->logDeprecation('Checking property Client::' . $name . ' is deprecated');
2017
            return isset($this->$name);
2018
        }
2019
2020
        switch ($name) {
2021
            case 'errno':
2022
            case 'errstr':
2023
            case 'method':
2024
            case 'server':
2025
            case 'port':
2026
            case 'path':
2027
                $this->logDeprecation('Checking property Client::' . $name . ' is deprecated');
2028
                return isset($this->$name);
2029
            default:
2030
                return false;
2031
        }
2032
    }
2033
2034
    public function __unset($name)
2035
    {
2036
        if (in_array($name, static::$options)) {
2037
            $this->logDeprecation('Unsetting property Client::' . $name . ' is deprecated');
2038
            unset($this->$name);
2039
            return;
2040
        }
2041
2042
        switch ($name) {
2043
            case 'errno':
2044
            case 'errstr':
2045
            case 'method':
2046
            case 'server':
2047
            case 'port':
2048
            case 'path':
2049
                $this->logDeprecation('Unsetting property Client::' . $name . ' is deprecated');
2050
                unset($this->$name);
2051
                return;
2052
            default:
2053
                /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout...
2054
                $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
2055
                trigger_error('Undefined property via __unset(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
2056
        }
2057
    }
2058
}
2059