Passed
Push — master ( c72dd6...e665f3 )
by Gaetano
07:31
created

Client::sendPayloadHTTP10()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1.2963

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 13
dl 0
loc 8
ccs 2
cts 6
cp 0.3333
crap 1.2963
rs 10

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\Helper\Charset;
6
use PhpXmlRpc\Helper\XMLParser;
7
use PhpXmlRpc\Traits\LoggerAware;
8
9
/**
10
 * Used to represent a client of an XML-RPC server.
11
 *
12
 * @todo add a setTimeout method, in the vein of other options which can be set to the Client
13
 */
14
class Client
15
{
16
    use LoggerAware;
17
18
    const USE_CURL_NEVER = 0;
19
    const USE_CURL_ALWAYS = 1;
20
    const USE_CURL_AUTO = 2;
21
22
    /** @var string */
23
    protected static $requestClass = '\\PhpXmlRpc\\Request';
24
    /** @var string */
25
    protected static $responseClass = '\\PhpXmlRpc\\Response';
26
27
    /**
28
     * @var int
29
     * @deprecated will be removed in the future
30
     */
31
    public $errno;
32
    /**
33
     * @var string
34
     * @deprecated will be removed in the future
35
     */
36
    public $errstr;
37
38
    /// @todo: do all the ones below need to be public?
39
40
    /**
41
     * @var string
42
     * @internal use getUrl
43
     */
44
    public $method = 'http';
45
    /**
46
     * @var string
47
     * @internal use getUrl
48
     */
49
    public $server;
50
    /**
51
     * @var int
52
     * @internal use getUrl
53
     */
54
    public $port = 0;
55
    /**
56
     * @var string
57
     * @internal use getUrl
58
     */
59
    public $path;
60
61
    /**
62
     * @var int
63
     * @internal use setDebug
64
     */
65
    public $debug = 0;
66
67
    /**
68
     * @var string
69
     * @internal use setCredentials
70
     */
71
    public $username = '';
72
    /**
73
     * @var string
74
     * @internal use setCredentials
75
     */
76
    public $password = '';
77
    /**
78
     * @var int
79
     * @internal use setCredentials
80
     */
81
    public $authtype = 1;
82
83
    /**
84
     * @var string
85
     * @internal use setCertificate
86
     */
87
    public $cert = '';
88
    /**
89
     * @var string
90
     * @internal use setCertificate
91
     */
92
    public $certpass = '';
93
    /**
94
     * @var string
95
     * @internal use setCaCertificate
96
     */
97
    public $cacert = '';
98
    /**
99
     * @var string
100
     * @internal use setCaCertificate
101
     */
102
    public $cacertdir = '';
103
    /**
104
     * @var string
105
     * @internal use setKey
106
     */
107
    public $key = '';
108
    /**
109
     * @var string
110
     * @internal use setKey
111
     */
112
    public $keypass = '';
113
    /**
114
     * @var bool
115
     * @internal use setSSLVerifyPeer
116
     */
117
    public $verifypeer = true;
118
    /**
119
     * @var int
120
     * @internal use setSSLVerifyHost
121
     */
122 2
    public $verifyhost = 2;
123
    /**
124 2
     * @var int
125 2
     * @internal use setSSLVersion
126
     */
127 2
    public $sslversion = 0; // corresponds to CURL_SSLVERSION_DEFAULT
128
129
    /**
130
     * @var string
131
     * @internal use setProxy
132
     */
133
    public $proxy = '';
134
    /**
135
     * @var int
136
     * @internal use setProxy
137
     */
138
    public $proxyport = 0;
139
    /**
140
     * @var string
141
     * @internal use setProxy
142
     */
143
    public $proxy_user = '';
144
    /**
145
     * @var string
146
     * @internal use setProxy
147
     */
148
    public $proxy_pass = '';
149
    /**
150
     * @var int
151
     * @internal use setProxy
152 718
     */
153
    public $proxy_authtype = 1;
154
155 718
    /**
156 7
     * @var array
157 7
     * @internal use setCookie
158 7
     */
159 7
    public $cookies = array();
160
161
    /**
162 7
     * @var array
163
     * @internal use setCurlOptions
164
     */
165 7
    public $extracurlopts = array();
166
167
    /**
168 7
     * @var int
169 7
     * @internal use setTimeout
170
     */
171 7
    public $timeout = 0;
172
173
    /**
174 7
     * @var int
175
     * @internal use setUseCurl
176
     */
177
    public $use_curl = self::USE_CURL_AUTO;
178 718
179
    /**
180
     * @var bool
181 718
     *
182
     * This determines whether the multicall() method will try to take advantage of the system.multicall xml-rpc method
183 718
     * to dispatch to the server an array of requests in a single http roundtrip or simply execute many consecutive http
184 718
     * calls. Defaults to FALSE, but it will be enabled automatically on the first failure of execution of
185 3
     * system.multicall.
186
     */
187 718
    public $no_multicall = false;
188 7
189
    /**
190
     * @var array
191
     *
192 718
     * List of http compression methods accepted by the client for responses.
193
     * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib.
194 711
     *
195
     * 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
196
     * decide the compression methods it supports. You might check for the presence of 'zlib' in the output of
197 718
     * curl_version() to determine whether compression is supported or not
198
     *
199
     * @internal use setAcceptedCompression
200
     */
201 718
    public $accepted_compression = array();
202
203
    /**
204 718
     * @var string|null
205
     *
206
     * Name of compression scheme to be used for sending requests.
207
     * Either null, 'gzip' or 'deflate'.
208
     *
209
     * @internal use setRequestCompression
210
     */
211
    public $request_compression = '';
212
213
    /**
214
     * @var bool
215
     *
216
     * Whether to use persistent connections for http 1.1 and https. Value set at constructor time
217 718
     */
218 718
    public $keepalive = false;
219
220
    /**
221
     * @var string[]
222
     *
223
     * Charset encodings that can be decoded without problems by the client. Value set at constructor time
224
     */
225
    public $accepted_charset_encodings = array();
226
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
    public $request_charset_encoding = '';
238
239
    /**
240
     * @var string
241
     *
242
     * Decides the content of Response objects returned by calls to send() and multicall().
243
     * Valid values are 'xmlrpcvals', 'phpvals' or 'xml'.
244
     *
245
     * Determines whether the value returned inside a Response object as results of calls to the send() and multicall()
246
     * methods will be a Value object, a plain php value or a raw xml string.
247
     * Allowed values are 'xmlrpcvals' (the default), 'phpvals' and 'xml'.
248
     * To allow the user to differentiate between a correct and a faulty response, fault responses will be returned as
249
     * Response objects in any case.
250
     * Note that the 'phpvals' setting will yield faster execution times, but some of the information from the original
251
     * response will be lost. It will be e.g. impossible to tell whether a particular php string value was sent by the
252 66
     * server as an xml-rpc string or base64 value.
253
     */
254 66
    public $return_type = XMLParser::RETURN_XMLRPCVALS;
255 66
256 66
    /**
257 66
     * @var string
258
     *
259
     * Sent to servers in http headers. Value set at constructor time.
260
     *
261
     * @internal use setUserAgent
262
     */
263
    public $user_agent;
264
265
    /**
266
     * CURL handle: used for keep-alive
267
     * @internal
268
     */
269
    public $xmlrpc_curl_handle = null;
270
271
    /**
272
     * @param string $path either the PATH part of the xml-rpc server URL, or complete server URL (in which case you
273
     *                     should use and empty string for all other parameters)
274
     *                     e.g. /xmlrpc/server.php
275
     *                     e.g. http://phpxmlrpc.sourceforge.net/server.php
276
     *                     e.g. https://james:[email protected]:444/xmlrpcserver?agent=007
277
     *                     e.g. h2://fast-and-secure-services.org/endpoint
278
     * @param string $server the server name / ip address
279
     * @param integer $port the port the server is listening on, when omitted defaults to 80 or 443 depending on
280
     *                      protocol used
281
     * @param string $method the http protocol variant: defaults to 'http'; 'https', 'http11', 'h2' and 'h2c' can
282
     *                       be used if CURL is installed. The value set here can be overridden in any call to $this->send().
283
     *                       Use 'h2' to make the lib attempt to use http/2 over a secure connection, and 'h2c'
284
     *                       for http/2 without tls. Note that 'h2c' will not use the h2c 'upgrade' method, and be
285
     *                       thus incompatible with any server/proxy not supporting http/2. This is because POST
286
     *                       request are not compatible with h2c upgrade.
287
     */
288
    public function __construct($path, $server = '', $port = '', $method = '')
289
    {
290
        // allow user to specify all params in $path
291
        if ($server == '' && $port == '' && $method == '') {
292
            $parts = parse_url($path);
293
            $server = $parts['host'];
294
            $path = isset($parts['path']) ? $parts['path'] : '';
295
            if (isset($parts['query'])) {
296
                $path .= '?' . $parts['query'];
297
            }
298
            if (isset($parts['fragment'])) {
299
                $path .= '#' . $parts['fragment'];
300
            }
301
            if (isset($parts['port'])) {
302
                $port = $parts['port'];
303
            }
304
            if (isset($parts['scheme'])) {
305
                $method = $parts['scheme'];
306
            }
307
            if (isset($parts['user'])) {
308
                $this->username = $parts['user'];
309
            }
310
            if (isset($parts['pass'])) {
311
                $this->password = $parts['pass'];
312
            }
313
        }
314
        if ($path == '' || $path[0] != '/') {
315
            $this->path = '/' . $path;
316 132
        } else {
317
            $this->path = $path;
318 132
        }
319 132
        $this->server = $server;
320
        if ($port != '') {
321
            $this->port = $port;
322
        }
323
        if ($method != '') {
324
            $this->method = $method;
325
        }
326
327
        // if ZLIB is enabled, let the client by default accept compressed responses
328 132
        if (function_exists('gzinflate') || (
329
                function_exists('curl_version') && (($info = curl_version()) &&
330 132
                    ((is_string($info) && strpos($info, 'zlib') !== null) || isset($info['libz_version'])))
331 132
            )
332
        ) {
333
            $this->accepted_compression = array('gzip', 'deflate');
334
        }
335
336
        // keepalives: enabled by default
337
        $this->keepalive = true;
338 132
339
        // by default the xml parser can support these 3 charset encodings
340 132
        $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII');
341 132
342
        // NB: this is disabled to avoid making all the requests sent huge... mbstring supports more than 80 charsets!
343
        //$ch = Charset::instance();
344
        //$this->accepted_charset_encodings = $ch->knownCharsets();
345
346
        // initialize user_agent string
347
        $this->user_agent = PhpXmlRpc::$xmlrpcName . ' ' . PhpXmlRpc::$xmlrpcVersion;
348
    }
349
350
    /**
351
     * Enable/disable the echoing to screen of the xml-rpc responses received. The default is not to output anything.
352
     *
353
     * The debugging information at level 1 includes the raw data returned from the XML-RPC server it was querying
354
     * (including bot HTTP headers and the full XML payload), and the PHP value the client attempts to create to
355 99
     * represent the value returned by the server.
356
     * At level 2, the complete payload of the xml-rpc request is also printed, before being sent to the server.
357 99
     * At level -1, the Response objects returned by send() calls will not carry information about the http response's
358 99
     * cookies, headers and body, which might save some memory
359 99
     *
360 99
     * This option can be very useful when debugging servers as it allows you to see exactly what the client sends and
361 99
     * the server returns. Never leave it enabled for production!
362 99
     *
363
     * @param integer $level values -1, 0, 1 and 2 are supported
364
     * @return $this
365
     */
366
    public function setDebug($level)
367
    {
368
        $this->debug = $level;
369
        return $this;
370
    }
371
372
    /**
373
     * Sets the username and password for authorizing the client to the server.
374
     *
375
     * With the default (HTTP) transport, this information is used for HTTP Basic authorization.
376
     * Note that username and password can also be set using the class constructor.
377
     * With HTTP 1.1 and HTTPS transport, NTLM and Digest authentication protocols are also supported. To enable them use
378
     * the constants CURLAUTH_DIGEST and CURLAUTH_NTLM as values for the auth type parameter.
379
     *
380
     * @param string $user username
381
     * @param string $password password
382
     * @param integer $authType auth type. See curl_setopt man page for supported auth types. Defaults to CURLAUTH_BASIC
383
     *                          (basic auth). Note that auth types NTLM and Digest will only work if the Curl php
384
     *                          extension is enabled.
385
     * @return $this
386
     */
387
    public function setCredentials($user, $password, $authType = 1)
388
    {
389
        $this->username = $user;
390
        $this->password = $password;
391
        $this->authtype = $authType;
392
        return $this;
393
    }
394
395
    /**
396
     * Set the optional certificate and passphrase used in SSL-enabled communication with a remote server.
397
     *
398
     * Note: to retrieve information about the client certificate on the server side, you will need to look into the
399
     * environment variables which are set up by the webserver. Different webservers will typically set up different
400
     * variables.
401
     *
402
     * @param string $cert the name of a file containing a PEM formatted certificate
403
     * @param string $certPass the password required to use it
404
     * @return $this
405
     */
406
    public function setCertificate($cert, $certPass = '')
407
    {
408
        $this->cert = $cert;
409
        $this->certpass = $certPass;
410
        return $this;
411
    }
412
413
    /**
414
     * Add a CA certificate to verify server with in SSL-enabled communication when SetSSLVerifypeer has been set to TRUE.
415
     *
416
     * See the php manual page about CURLOPT_CAINFO for more details.
417
     *
418 580
     * @param string $caCert certificate file name (or dir holding certificates)
419
     * @param bool $isDir set to true to indicate cacert is a dir. defaults to false
420 580
     * @return $this
421 580
     */
422
    public function setCaCertificate($caCert, $isDir = false)
423
    {
424
        if ($isDir) {
425
            $this->cacertdir = $caCert;
426
        } else {
427 580
            $this->cacert = $caCert;
428
        }
429 580
        return $this;
430
    }
431
432
    /**
433
     * Set attributes for SSL communication: private SSL key.
434
     *
435
     * NB: does not work in older php/curl installs.
436
     * Thanks to Daniel Convissor.
437
     *
438
     * @param string $key The name of a file containing a private SSL key
439
     * @param string $keyPass The secret password needed to use the private SSL key
440
     * @return $this
441
     */
442
    public function setKey($key, $keyPass)
443
    {
444
        $this->key = $key;
445
        $this->keypass = $keyPass;
446 66
        return $this;
447
    }
448 66
449 66
    /**
450
     * Set attributes for SSL communication: verify the remote host's SSL certificate, and cause the connection to fail
451
     * if the cert verification fails.
452
     *
453
     * By default, verification is enabled.
454
     * To specify custom SSL certificates to validate the server with, use the setCaCertificate method.
455
     *
456
     * @param bool $i enable/disable verification of peer certificate
457
     * @return $this
458
     */
459
    public function setSSLVerifyPeer($i)
460
    {
461
        $this->verifypeer = $i;
462
        return $this;
463
    }
464
465
    /**
466
     * Set attributes for SSL communication: verify the remote host's SSL certificate's common name (CN).
467
     *
468
     * Note that support for value 1 has been removed in cURL 7.28.1
469
     *
470
     * @param int $i Set to 1 to only the existence of a CN, not that it matches
471
     * @return $this
472
     */
473
    public function setSSLVerifyHost($i)
474
    {
475
        $this->verifyhost = $i;
476
        return $this;
477
    }
478
479
    /**
480
     * Set attributes for SSL communication: SSL version to use. Best left at 0 (default value): let cURL decide
481
     *
482
     * @param int $i
483
     * @return $this
484
     */
485
    public function setSSLVersion($i)
486
    {
487
        $this->sslversion = $i;
488
        return $this;
489
    }
490
491
    /**
492
     * Set proxy info.
493
     *
494
     * NB: CURL versions before 7.11.10 cannot use a proxy to communicate with https servers.
495 697
     *
496
     * @param string $proxyHost
497
     * @param string $proxyPort Defaults to 8080 for HTTP and 443 for HTTPS
498
     * @param string $proxyUsername Leave blank if proxy has public access
499 697
     * @param string $proxyPassword Leave blank if proxy has public access
500 117
     * @param int $proxyAuthType defaults to CURLAUTH_BASIC (Basic authentication protocol); set to constant CURLAUTH_NTLM
501
     *                           to use NTLM auth with proxy (has effect only when the client uses the HTTP 1.1 protocol)
502
     * @return $this
503 697
     */
504
    public function setProxy($proxyHost, $proxyPort, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1)
505 66
    {
506
        $this->proxy = $proxyHost;
507 66
        $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...
508 697
        $this->proxy_user = $proxyUsername;
509 28
        $this->proxy_pass = $proxyPassword;
510 28
        $this->proxy_authtype = $proxyAuthType;
511 28
        return $this;
512
    }
513
514
    /**
515 697
     * Enables/disables reception of compressed xml-rpc responses.
516
     *
517
     * This requires the "zlib" extension to be enabled in your php install. If it is, by default xmlrpc_client
518
     * instances will enable reception of compressed content.
519 697
     * Note that enabling reception of compressed responses merely adds some standard http headers to xml-rpc requests.
520 697
     * It is up to the xml-rpc server to return compressed responses when receiving such requests.
521
     *
522 697
     * @param string $compMethod either 'gzip', 'deflate', 'any' or ''
523 322
     * @return $this
524 322
     */
525 322
    public function setAcceptedCompression($compMethod)
526 322
    {
527
        if ($compMethod == 'any') {
528 322
            $this->accepted_compression = array('gzip', 'deflate');
529 322
        } 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...
530 322
            $this->accepted_compression = array();
531 322
        } else {
532 322
            $this->accepted_compression = array($compMethod);
533 322
        }
534 322
        return $this;
535 322
    }
536 322
537 322
    /**
538 322
     * Enables/disables http compression of xml-rpc request.
539 322
     *
540
     * This requires the "zlib" extension to be enabled in your php install.
541 322
     * Take care when sending compressed requests: servers might not support them (and automatic fallback to
542 322
     * uncompressed requests is not yet implemented).
543 322
     *
544 322
     * @param string $compMethod either 'gzip', 'deflate' or ''
545 322
     * @return $this
546
     */
547
    public function setRequestCompression($compMethod)
548
    {
549 375
        $this->request_compression = $compMethod;
550 375
        return $this;
551 375
    }
552 375
553
    /**
554 375
     * Adds a cookie to list of cookies that will be sent to server with every further request (useful e.g. for keeping
555 375
     * session info outside of the xml-rpc payload).
556 375
     *
557 375
     * NB: By default cookies are sent using the 'original/netscape' format, which is also the same as the RFC 2965;
558 375
     * setting any param but name and value will turn the cookie into a 'version 1' cookie (i.e. RFC 2109 cookie) that
559 375
     * might not be fully supported by the server. Note that RFC 2109 has currently 'historic' status...
560 375
     *
561 375
     * @param string $name nb: will not be escaped in the request's http headers. Take care not to use CTL chars or
562 375
     *                     separators!
563 375
     * @param string $value
564 375
     * @param string $path leave this empty unless the xml-rpc server only accepts RFC 2109 cookies
565 375
     * @param string $domain leave this empty unless the xml-rpc server only accepts RFC 2109 cookies
566
     * @param int $port leave this empty unless the xml-rpc server only accepts RFC 2109 cookies
567 375
     * @return $this
568 375
     *
569 375
     * @todo check correctness of urlencoding cookie value (copied from php way of doing it, but php is generally sending
570
     *       response not requests. We do the opposite...)
571
     * @todo strip invalid chars from cookie name? As per RFC6265, we should follow RFC2616, Section 2.2
572
     */
573 697
    public function setCookie($name, $value = '', $path = '', $domain = '', $port = null)
574
    {
575
        $this->cookies[$name]['value'] = rawurlencode($value);
576
        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...
577
            $this->cookies[$name]['path'] = $path;
578
            $this->cookies[$name]['domain'] = $domain;
579
            $this->cookies[$name]['port'] = $port;
580
            $this->cookies[$name]['version'] = 1;
581
        } else {
582
            $this->cookies[$name]['version'] = 0;
583
        }
584
        return $this;
585
    }
586
587
    /**
588
     * Directly set cURL options, for extra flexibility (when in cURL mode).
589
     *
590
     * It allows e.g. to bind client to a specific IP interface / address.
591
     *
592
     * @param array $options
593
     * @return $this
594
     */
595
    public function setCurlOptions($options)
596
    {
597
        $this->extracurlopts = $options;
598
        return $this;
599
    }
600
601
    /**
602
     * @param int $useCurlMode self::USE_CURL_ALWAYS, self::USE_CURL_AUTO or self::USE_CURL_NEVER
603
     * @return $this
604
     */
605
    public function setUseCurl($useCurlMode)
606
    {
607
        $this->use_curl = $useCurlMode;
608
        return $this;
609
    }
610
611
612
    /**
613
     * Set user-agent string that will be used by this client instance in http headers sent to the server.
614
     *
615
     * The default user agent string includes the name of this library and the version number.
616
     *
617
     * @param string $agentString
618
     * @return $this
619
     */
620
    public function setUserAgent($agentString)
621
    {
622
        $this->user_agent = $agentString;
623
        return $this;
624
    }
625
626
    /**
627
     * @param int $timeout
628
     * @return $this
629
     */
630
    public function setTimeout($timeout)
631
    {
632
        $this->timeout = $timeout;
633
        return $this;
634
    }
635
636
    /**
637
     * @return string
638
     */
639
    public function getUrl()
640
    {
641
        $url = $this->method . '://' . $this->server;
642
        if (($this->port = 80 && in_array($this->method, array('http', 'http10', 'http11', 'h2c'))) &&
0 ignored issues
show
Documentation Bug introduced by
The property $port was declared of type integer, but 80 && in_array($this->me...p10', 'http11', 'h2c')) is of type boolean. 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...
643
            ($this->port = 443 && in_array($this->method, array('https', 'h2')))) {
644
            return $url . $this->path;
645
        } else {
646
            return $url . ':' . $this->port . $this->path;
0 ignored issues
show
Bug introduced by
Are you sure $this->port of type false can be used in concatenation? ( Ignorable by Annotation )

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

646
            return $url . ':' . /** @scrutinizer ignore-type */ $this->port . $this->path;
Loading history...
647
        }
648
    }
649
650
    /**
651
     * Send an xml-rpc request to the server.
652
     *
653
     * @param Request|Request[]|string $req The Request object, or an array of requests for using multicall, or the
654
     *                                      complete xml representation of a request.
655
     *                                      When sending an array of Request objects, the client will try to make use of
656
     *                                      a single 'system.multicall' xml-rpc method call to forward to the server all
657
     *                                      the requests in a single HTTP round trip, unless $this->no_multicall has
658
     *                                      been previously set to TRUE (see the multicall method below), in which case
659
     *                                      many consecutive xml-rpc requests will be sent. The method will return an
660
     *                                      array of Response objects in both cases.
661
     *                                      The third variant allows to build by hand (or any other means) a complete
662
     *                                      xml-rpc request message, and send it to the server. $req should be a string
663
     *                                      containing the complete xml representation of the request. It is e.g. useful
664 375
     *                                      when, for maximal speed of execution, the request is serialized into a
665
     *                                      string using the native php xml-rpc functions (see http://www.php.net/xmlrpc)
666
     * @param integer $timeout deprecated. Connection timeout, in seconds, If unspecified, the timeout set with setTimeout
667
     *                         will be used. If that is 0, a platform specific timeout will apply.
668
     *                         This timeout value is passed to fsockopen(). It is also used for detecting server
669
     *                         timeouts during communication (i.e. if the server does not send anything to the client
670
     *                         for $timeout seconds, the connection will be closed).
671 375
     * @param string $method deprecated. Use the same value in the constructor instead.
672 373
     *                       Valid values are 'http', 'http11', 'https', 'h2' and 'h2c'. If left empty,
673
     *                       the http protocol chosen during creation of the object will be used.
674
     *                       Use 'h2' to make the lib attempt to use http/2 over a secure connection, and 'h2c'
675
     *                       for http/2 without tls. Note that 'h2c' will not use the h2c 'upgrade' method, and be
676 375
     *                       thus incompatible with any server/proxy not supporting http/2. This is because POST
677 346
     *                       request are not compatible with h2c upgrade.
678
     * @return Response|Response[] Note that the client will always return a Response object, even if the call fails
679
     *
680 375
     * @todo allow throwing exceptions instead of returning responses in case of failed calls and/or Fault responses
681
     * @todo refactor: we now support many options besides connection timeout and http version to use. Why only privilege those?
682 375
     */
683 375
    public function send($req, $timeout = 0, $method = '')
684 64
    {
685 32
        // if user does not specify http protocol, use native method of this client
686 32
        // (i.e. method set during call to constructor)
687 32
        if ($method == '') {
688 32
            $method = $this->method;
689
        }
690
691 32
        if ($timeout == 0) {
692 32
            $timeout = $this->timeout;
693 32
        }
694 32
695
        if (is_array($req)) {
696
            // $req is an array of Requests
697
            $r = $this->multicall($req, $timeout, $method);
698
699
            return $r;
700 375
        } elseif (is_string($req)) {
701 375
            $n = new  static::$requestClass('');
702 32
            $n->payload = $req;
703 32
            $req = $n;
704
        }
705
706
        // where req is a Request
707
        $req->setDebug($this->debug);
708 375
709 375
        /// @todo we could be smarter about this and force usage of curl in scenarios where it is both available and
710 73
        ///       needed, such as digest or ntlm auth. Do not attempt to use it for https if not present
711
        $useCurl = ($this->use_curl == self::USE_CURL_ALWAYS) || ($this->use_curl == self::USE_CURL_AUTO &&
712
            (in_array($method, array('https', 'http11', 'h2c', 'h2'))));
713 375
714 375
        if ($useCurl) {
715 32
            $r = $this->sendPayloadCURL(
716
                $req,
717
                $this->server,
718 32
                $this->port,
719 32
                $timeout,
720 32
                $this->username,
721 32
                $this->password,
722 32
                $this->authtype,
723
                $this->cert,
724
                $this->certpass,
725
                $this->cacert,
726 32
                $this->cacertdir,
727
                $this->proxy,
728
                $this->proxyport,
729 343
                $this->proxy_user,
730 343
                $this->proxy_pass,
731 343
                $this->proxy_authtype,
732 343
                // bc
733
                $method == 'http11' ? 'http' : $method,
734
                $this->keepalive,
735
                $this->key,
736 375
                $this->keypass,
737 375
                $this->sslversion
738 309
            );
739 309
        } else {
740 309
            $r = $this->sendPayloadSocket(
741
                $req,
742
                $this->server,
743
                $this->port,
744
                $timeout,
745
                $this->username,
746
                $this->password,
747
                $this->authtype,
748
                $this->cert,
749
                $this->certpass,
750
                $this->cacert,
751
                $this->cacertdir,
752
                $this->proxy,
753 309
                $this->proxyport,
754
                $this->proxy_user,
755
                $this->proxy_pass,
756 309
                $this->proxy_authtype,
757
                $method,
758
                $this->key,
759
                $this->keypass,
760 375
                $this->sslversion
761 375
            );
762
        }
763
764
        return $r;
765
    }
766 375
767 375
    /**
768 375
     * @deprecated
769 375
     *
770 375
     * @param Request $req
771 375
     * @param string $server
772 375
     * @param int $port
773 375
     * @param int $timeout
774 375
     * @param string $username
775 375
     * @param string $password
776 375
     * @param int $authType
777 375
     * @param string $proxyHost
778
     * @param int $proxyPort
779 375
     * @param string $proxyUsername
780 2
     * @param string $proxyPassword
781
     * @param int $proxyAuthType
782
     * @param string $method
783 375
     * @return Response
784 375
     */
785 32
    protected function sendPayloadHTTP10($req, $server, $port, $timeout = 0, $username = '', $password = '',
786
        $authType = 1, $proxyHost = '', $proxyPort = 0, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1,
787
        $method='http')
788
    {
789
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
790
791 32
        return $this->sendPayloadSocket($req, $server, $port, $timeout, $username, $password, $authType, null, null,
792
            null, null, $proxyHost, $proxyPort, $proxyUsername, $proxyPassword, $proxyAuthType, $method);
793
    }
794 32
795
    /**
796
     * @deprecated
797 32
     *
798
     * @param Request $req
799
     * @param string $server
800 32
     * @param int $port
801 32
     * @param int $timeout
802
     * @param string $username
803
     * @param string $password
804 375
     * @param int $authType
805
     * @param string $cert
806 375
     * @param string $certPass
807 64
     * @param string $caCert
808
     * @param string $caCertDir
809 311
     * @param string $proxyHost
810
     * @param int $proxyPort
811
     * @param string $proxyUsername
812 375
     * @param string $proxyPassword
813 375
     * @param int $proxyAuthType
814
     * @param bool $keepAlive
815 375
     * @param string $key
816 375
     * @param string $keyPass
817 375
     * @param int $sslVersion
818 374
     * @return Response
819 367
     */
820
    protected function sendPayloadHTTPS($req, $server, $port, $timeout = 0, $username = '',  $password = '',
821
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
822 1
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $keepAlive = false, $key = '', $keyPass = '',
823
        $sslVersion = 0)
824
    {
825
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
826
827 1
        return $this->sendPayloadCURL($req, $server, $port, $timeout, $username,
828 1
            $password, $authType, $cert, $certPass, $caCert, $caCertDir, $proxyHost, $proxyPort,
829
            $proxyUsername, $proxyPassword, $proxyAuthType, 'https', $keepAlive, $key, $keyPass, $sslVersion);
830 1
    }
831
832
    /**
833 374
     * @param Request $req
834
     * @param string $server
835
     * @param int $port
836
     * @param int $timeout
837
     * @param string $username
838
     * @param string $password
839
     * @param int $authType only value supported is 1
840
     * @param string $cert
841
     * @param string $certPass
842
     * @param string $caCert
843 374
     * @param string $caCertDir
844
     * @param string $proxyHost
845
     * @param int $proxyPort
846
     * @param string $proxyUsername
847 374
     * @param string $proxyPassword
848 374
     * @param int $proxyAuthType only value supported is 1
849 374
     * @param string $method 'http' (synonym for 'http10'), 'http10' or 'https'
850
     * @param string $key
851 374
     * @param string $keyPass @todo not implemented yet.
852
     * @param int $sslVersion @todo not implemented yet. See http://php.net/manual/en/migration56.openssl.php
853 374
     * @return Response
854
     *
855
     * @todo refactor: we get many options for the call passed in, but some we use from $this. We should clean that up
856
     */
857
    protected function sendPayloadSocket($req, $server, $port, $timeout = 0, $username = '', $password = '',
858
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
859
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method='http', $key = '', $keyPass = '',
0 ignored issues
show
Unused Code introduced by
The parameter $keyPass 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

859
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method='http', $key = '', /** @scrutinizer ignore-unused */ $keyPass = '',

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...
860
        $sslVersion = 0)
0 ignored issues
show
Unused Code introduced by
The parameter $sslVersion 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

860
        /** @scrutinizer ignore-unused */ $sslVersion = 0)

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...
861
    {
862
        /// @todo log a warning if passed an unsupported method
863
864
        if ($port == 0) {
865
            $port = ($method === 'https') ? 443 : 80;
866
        }
867
868
        // Only create the payload if it was not created previously
869
        /// @todo what if the request's payload was created with a different encoding?
870
        if (empty($req->payload)) {
871
            $req->serialize($this->request_charset_encoding);
872
        }
873
874
        $payload = $req->payload;
875
        // Deflate request body and set appropriate request headers
876
        $encodingHdr = '';
877
        if (function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate')) {
878
            if ($this->request_compression == 'gzip') {
879
                $a = @gzencode($payload);
880
                if ($a) {
881
                    $payload = $a;
882
                    $encodingHdr = "Content-Encoding: gzip\r\n";
883
                }
884
            } else {
885
                $a = @gzcompress($payload);
886 322
                if ($a) {
887
                    $payload = $a;
888
                    $encodingHdr = "Content-Encoding: deflate\r\n";
889
                }
890
            }
891 322
        }
892
893
        // thanks to Grant Rauscher <[email protected]> for this
894
        $credentials = '';
895 322
        if ($username != '') {
896
            $credentials = 'Authorization: Basic ' . base64_encode($username . ':' . $password) . "\r\n";
897 96
            if ($authType != 1) {
898 96
                /// @todo make this a proper error, i.e. return a failure
899
                $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported with HTTP 1.0');
900
            }
901
        }
902
903
        $acceptedEncoding = '';
904 322
        if (is_array($this->accepted_compression) && count($this->accepted_compression)) {
905 322
            $acceptedEncoding = 'Accept-Encoding: ' . implode(', ', $this->accepted_compression) . "\r\n";
906
        }
907
908
        $proxyCredentials = '';
909
        if ($proxyHost) {
910 322
            if ($proxyPort == 0) {
911
                $proxyPort = 8080;
912
            }
913
            $connectServer = $proxyHost;
914
            $connectPort = $proxyPort;
915 322
            $transport = 'tcp';
916
            /// @todo check: should we not use https in some cases?
917 322
            $uri = 'http://' . $server . ':' . $port . $this->path;
918
            if ($proxyUsername != '') {
919
                if ($proxyAuthType != 1) {
920
                    /// @todo make this a proper error, i.e. return a failure
921
                    $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported with HTTP 1.0');
922
                }
923
                $proxyCredentials = 'Proxy-Authorization: Basic ' . base64_encode($proxyUsername . ':' . $proxyPassword) . "\r\n";
924
            }
925
        } else {
926
            $connectServer = $server;
927
            $connectPort = $port;
928
            $transport = ($method === 'https') ? 'tls' : 'tcp';
929 322
            $uri = $this->path;
930
        }
931
932 1
        // Cookie generation, as per rfc2965 (version 1 cookies) or netscape's rules (version 0 cookies)
933 1
        $cookieHeader = '';
934 1
        if (count($this->cookies)) {
935 1
            $version = '';
936 1
            foreach ($this->cookies as $name => $cookie) {
937
                if ($cookie['version']) {
938
                    $version = ' $Version="' . $cookie['version'] . '";';
939 322
                    $cookieHeader .= ' ' . $name . '="' . $cookie['value'] . '";';
940 160
                    if ($cookie['path']) {
941
                        $cookieHeader .= ' $Path="' . $cookie['path'] . '";';
942 322
                    }
943
                    if ($cookie['domain']) {
944 322
                        $cookieHeader .= ' $Domain="' . $cookie['domain'] . '";';
945
                    }
946
                    if ($cookie['port']) {
947
                        $cookieHeader .= ' $Port="' . $cookie['port'] . '";';
948
                    }
949
                } else {
950 322
                    $cookieHeader .= ' ' . $name . '=' . $cookie['value'] . ";";
951
                }
952
            }
953 323
            $cookieHeader = 'Cookie:' . $version . substr($cookieHeader, 0, -1) . "\r\n";
954
        }
955
956
        // omit port if default
957
        if (($port == 80 && in_array($method, array('http', 'http10'))) || ($port == 443 && $method == 'https')) {
958 323
            $port =  '';
959 322
        } else {
960 226
            $port = ':' . $port;
961
        }
962 96
963
        $op = 'POST ' . $uri . " HTTP/1.0\r\n" .
964
            'User-Agent: ' . $this->user_agent . "\r\n" .
965
            'Host: ' . $server . $port . "\r\n" .
966
            $credentials .
967 323
            $proxyCredentials .
968 302
            $acceptedEncoding .
969
            $encodingHdr .
970
            'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings) . "\r\n" .
971
            $cookieHeader .
972 323
            'Content-Type: ' . $req->content_type . "\r\nContent-Length: " .
973 323
            strlen($payload) . "\r\n\r\n" .
974 64
            $payload;
975 32
976 32
        if ($this->debug > 1) {
977 32
            $this->getLogger()->debug("---SENDING---\n$op\n---END---");
978 32
        }
979
980
        $contextOptions = array();
981 32
        if ($method == 'https') {
982 32
            if ($cert != '') {
983 32
                $contextOptions['ssl']['local_cert'] = $cert;
984 64
                if ($certPass != '') {
985
                    $contextOptions['ssl']['passphrase'] = $certPass;
986
                }
987
            }
988 259
            if ($caCert != '') {
989
                $contextOptions['ssl']['cafile'] = $caCert;
990
            }
991 323
            if ($caCertDir != '') {
992 323
                $contextOptions['ssl']['capath'] = $caCertDir;
993 64
            }
994
            if ($key != '') {
995 259
                $contextOptions['ssl']['local_pk'] = $key;
996 32
            }
997
            $contextOptions['ssl']['verify_peer'] = $this->verifypeer;
998
            $contextOptions['ssl']['verify_peer_name'] = $this->verifypeer;
999 227
        }
1000
1001
        $context = stream_context_create($contextOptions);
1002 323
1003 323
        if ($timeout <= 0) {
1004 322
            $connectTimeout = ini_get('default_socket_timeout');
1005
        } else {
1006
            $connectTimeout = $timeout;
1007 56
        }
1008
1009
        $this->errno = 0;
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errno has been deprecated: will be removed in the future ( Ignorable by Annotation )

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

1009
        /** @scrutinizer ignore-deprecated */ $this->errno = 0;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1010
        $this->errstr = '';
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

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

1010
        /** @scrutinizer ignore-deprecated */ $this->errstr = '';

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1011 323
1012
        $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr, $connectTimeout,
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errno has been deprecated: will be removed in the future ( Ignorable by Annotation )

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

1012
        $fp = @stream_socket_client("$transport://$connectServer:$connectPort", /** @scrutinizer ignore-deprecated */ $this->errno, $this->errstr, $connectTimeout,

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

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

1012
        $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, /** @scrutinizer ignore-deprecated */ $this->errstr, $connectTimeout,

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
Bug introduced by
It seems like $connectTimeout can also be of type string; however, parameter $timeout of stream_socket_client() does only seem to accept double|null, 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

1012
        $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr, /** @scrutinizer ignore-type */ $connectTimeout,
Loading history...
1013 323
            STREAM_CLIENT_CONNECT, $context);
1014
        if ($fp) {
0 ignored issues
show
introduced by
$fp is of type false|resource, thus it always evaluated to false.
Loading history...
1015
            if ($timeout > 0) {
1016
                stream_set_timeout($fp, $timeout, 0);
1017 323
            }
1018
        } else {
1019 323
            if ($this->errstr == '') {
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

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

1019
            if (/** @scrutinizer ignore-deprecated */ $this->errstr == '') {

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1020
                $err = error_get_last();
1021 323
                $this->errstr = $err['message'];
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

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

1021
                /** @scrutinizer ignore-deprecated */ $this->errstr = $err['message'];

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1022
            }
1023
1024 323
            $this->errstr = 'Connect error: ' . $this->errstr;
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

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

1024
            /** @scrutinizer ignore-deprecated */ $this->errstr = 'Connect error: ' . $this->errstr;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1025
            $r = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr . ' (' . $this->errno . ')');
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errno has been deprecated: will be removed in the future ( Ignorable by Annotation )

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

1025
            $r = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr . ' (' . /** @scrutinizer ignore-deprecated */ $this->errno . ')');

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

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

1025
            $r = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], /** @scrutinizer ignore-deprecated */ $this->errstr . ' (' . $this->errno . ')');

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1026
1027
            return $r;
1028 323
        }
1029
1030
        if (!fputs($fp, $op, strlen($op))) {
1031 66
            fclose($fp);
1032 64
            $this->errstr = 'Write error';
1033
            $r = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr);
1034 2
1035
            return $r;
1036
        }
1037
1038 323
        // Close socket before parsing.
1039
        // It should yield slightly better execution times, and make easier recursive calls (e.g. to follow http redirects)
1040 323
        $ipd = '';
1041 161
        do {
1042
            // shall we check for $data === FALSE?
1043
            // as per the manual, it signals an error
1044 323
            $ipd .= fread($fp, 32768);
1045 64
        } while (!feof($fp));
1046
        fclose($fp);
1047
1048
        $r = $req->parseResponse($ipd, false, $this->return_type);
1049
1050 323
        return $r;
1051
    }
1052 323
1053
    /**
1054 323
     * Contributed by Justin Miller <[email protected]>
1055 272
     * Requires curl to be built into PHP
1056
     * NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers!
1057
     *
1058 323
     * @param Request $req
1059 323
     * @param string $server
1060 32
     * @param int $port
1061 32
     * @param int $timeout
1062 291
     * @param string $username
1063
     * @param string $password
1064
     * @param int $authType
1065 291
     * @param string $cert
1066 32
     * @param string $certPass
1067 32
     * @param string $caCert
1068 259
     * @param string $caCertDir
1069 32
     * @param string $proxyHost
1070 32
     * @param int $proxyPort
1071
     * @param string $proxyUsername
1072
     * @param string $proxyPassword
1073 323
     * @param int $proxyAuthType
1074 32
     * @param string $method 'http' (let curl decide), 'http10', 'http11', 'https', 'h2c' or 'h2'
1075 32
     * @param bool $keepAlive
1076 32
     * @param string $key
1077
     * @param string $keyPass
1078
     * @param int $sslVersion
1079
     * @return Response
1080
     *
1081
     * @todo refactor: we get many options for the call passed in, but some we use from $this. We should clean that up
1082 323
     */
1083
    protected function sendPayloadCURL($req, $server, $port, $timeout = 0, $username = '', $password = '',
1084 96
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
1085
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'https', $keepAlive = false, $key = '',
1086
        $keyPass = '', $sslVersion = 0)
1087
    {
1088 96
        if (!function_exists('curl_init')) {
1089
            $this->errstr = 'CURL unavailable on this install';
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

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

1089
            /** @scrutinizer ignore-deprecated */ $this->errstr = 'CURL unavailable on this install';

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1090
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_curl'], PhpXmlRpc::$xmlrpcstr['no_curl']);
1091
        }
1092 96
        if ($method == 'https' || $method == 'h2') {
1093
            // q: what about installs where we get back a string, but curl is linked to other ssl libs than openssl?
1094 96
            if (($info = curl_version()) &&
1095
                ((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...
1096
            ) {
1097 96
                $this->errstr = 'SSL unavailable on this install';
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

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

1097
                /** @scrutinizer ignore-deprecated */ $this->errstr = 'SSL unavailable on this install';

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1098
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_ssl'], PhpXmlRpc::$xmlrpcstr['no_ssl']);
1099
            }
1100
        }
1101 96
        if (($method == 'h2' && !defined('CURL_HTTP_VERSION_2_0')) ||
1102
            ($method == 'h2c' && !defined('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE'))) {
1103
            $this->errstr = 'HTTP/2 unavailable on this install';
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

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

1103
            /** @scrutinizer ignore-deprecated */ $this->errstr = 'HTTP/2 unavailable on this install';

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1104
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_http2'], PhpXmlRpc::$xmlrpcstr['no_http2']);
1105 96
        }
1106
1107
        $curl = $this->prepareCurlHandle($req, $server, $port, $timeout, $username, $password,
1108
            $authType, $cert, $certPass, $caCert, $caCertDir, $proxyHost, $proxyPort,
1109
            $proxyUsername, $proxyPassword, $proxyAuthType, $method, $keepAlive, $key,
1110 96
            $keyPass, $sslVersion);
1111
1112 96
        if (!$curl) {
1113
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] . ': error during curl initialization. Check php error log for details');
1114
        }
1115
1116 323
        $result = curl_exec($curl);
1117 64
1118
        if ($this->debug > 1) {
1119
            $message = "---CURL INFO---\n";
1120 64
            foreach (curl_getinfo($curl) as $name => $val) {
1121 64
                if (is_array($val)) {
1122
                    $val = implode("\n", $val);
1123
                }
1124
                $message .= $name . ': ' . $val . "\n";
1125
            }
1126
            $message .= '---END---';
1127
            $this->getLogger()->debug($message);
1128
        }
1129
1130
        if (!$result) {
1131
            /// @todo we should use a better check here - what if we get back '' or '0'?
1132
1133 323
            $this->errstr = 'no response';
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

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

1133
            /** @scrutinizer ignore-deprecated */ $this->errstr = 'no response';

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1134 271
            $resp = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] . ': ' . curl_error($curl));
1135 271
            curl_close($curl);
1136 271
            if ($keepAlive) {
1137
                $this->xmlrpc_curl_handle = null;
1138 271
            }
1139
        } else {
1140
            if (!$keepAlive) {
1141 323
                curl_close($curl);
1142
            }
1143
            $resp = $req->parseResponse($result, true, $this->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

1143
            $resp = $req->parseResponse(/** @scrutinizer ignore-type */ $result, true, $this->return_type);
Loading history...
1144
            if ($keepAlive) {
1145 323
                /// @todo if we got back a 302 or 308, we should not reuse the curl handle for later calls
1146
                if ($resp->faultCode() == PhpXmlRpc::$xmlrpcerr['http_error']) {
1147
                    curl_close($curl);
1148
                    $this->xmlrpc_curl_handle = null;
1149 323
                }
1150
            }
1151
        }
1152
1153
        return $resp;
1154
    }
1155
1156
    protected function prepareCurlHandle($req, $server, $port, $timeout = 0, $username = '', $password = '',
1157
         $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
1158
         $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'https', $keepAlive = false, $key = '',
1159
         $keyPass = '', $sslVersion = 0)
1160
    {
1161
        if ($port == 0) {
1162
            if (in_array($method, array('http', 'http10', 'http11', 'h2c'))) {
1163
                $port = 80;
1164
            } else {
1165
                $port = 443;
1166
            }
1167
        }
1168
1169
        // Only create the payload if it was not created previously
1170
        if (empty($req->payload)) {
1171
            $req->serialize($this->request_charset_encoding);
1172
        }
1173
1174 66
        // Deflate request body and set appropriate request headers
1175
        $payload = $req->payload;
1176 66
        $encodingHdr = '';
1177
        /// @todo test for existence of proper function, in case of polyfills
1178
        if (function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate')) {
1179 66
            if ($this->request_compression == 'gzip') {
1180 45
                $a = @gzencode($payload);
1181 45
                if ($a) {
1182
                    $payload = $a;
1183 45
                    $encodingHdr = 'Content-Encoding: gzip';
1184
                }
1185
            } else {
1186
                $a = @gzcompress($payload);
1187
                if ($a) {
1188
                    $payload = $a;
1189
                    $encodingHdr = 'Content-Encoding: deflate';
1190
                }
1191
            }
1192
        }
1193
1194
        if (!$keepAlive || !$this->xmlrpc_curl_handle) {
1195
            if ($method == 'http11' || $method == 'http10' || $method == 'h2c') {
1196
                $protocol = 'http';
1197
            } else {
1198
                if ($method == 'h2') {
1199
                    $protocol = 'https';
1200
                } else {
1201 23
                    // http, https
1202
                    $protocol = $method;
1203
                    if (strpos($protocol, ':') !== false) {
1204 23
                        $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ": warning - attempted hacking attempt?. The curl protocol requested for the call is: '$protocol'");
1205 23
                        return false;
1206
                    }
1207
                }
1208
            }
1209 23
            $curl = curl_init($protocol . '://' . $server . ':' . $port . $this->path);
1210 23
            if (!$curl) {
1211
                return false;
1212
            }
1213
            if ($keepAlive) {
1214
                $this->xmlrpc_curl_handle = $curl;
1215
            }
1216
        } else {
1217
            $curl = $this->xmlrpc_curl_handle;
1218
        }
1219
1220
        // results into variable
1221 23
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
1222
1223
        if ($this->debug > 1) {
1224
            curl_setopt($curl, CURLOPT_VERBOSE, true);
1225
            /// @todo redirect curlopt_stderr to some stream which can be piped to the logger
1226
        }
1227
        curl_setopt($curl, CURLOPT_USERAGENT, $this->user_agent);
1228
        // required for XMLRPC: post the data
1229
        curl_setopt($curl, CURLOPT_POST, 1);
1230
        // the data
1231
        curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
1232
1233
        // return the header too
1234
        curl_setopt($curl, CURLOPT_HEADER, 1);
1235 45
1236
        // NB: if we set an empty string, CURL will add http header indicating
1237
        // ALL methods it is supporting. This is possibly a better option than letting the user tell what curl can / cannot do...
1238 45
        if (is_array($this->accepted_compression) && count($this->accepted_compression)) {
1239 45
            //curl_setopt($curl, CURLOPT_ENCODING, implode(',', $this->accepted_compression));
1240 45
            // empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1241 45
            if (count($this->accepted_compression) == 1) {
1242 45
                curl_setopt($curl, CURLOPT_ENCODING, $this->accepted_compression[0]);
1243 45
            } else {
1244 45
                curl_setopt($curl, CURLOPT_ENCODING, '');
1245
            }
1246 45
        }
1247 45
        // extra headers
1248
        $headers = array('Content-Type: ' . $req->content_type, 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings));
1249 45
        // if no keepalive is wanted, let the server know it in advance
1250 45
        if (!$keepAlive) {
1251
            $headers[] = 'Connection: close';
1252
        }
1253 45
        // request compression header
1254
        if ($encodingHdr) {
1255 45
            $headers[] = $encodingHdr;
1256
        }
1257
1258
        // Fix the HTTP/1.1 417 Expectation Failed Bug (curl by default adds a 'Expect: 100-continue' header when POST
1259
        // size exceeds 1025 bytes, apparently)
1260
        $headers[] = 'Expect:';
1261 45
1262
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
1263 45
        // timeout is borked
1264
        if ($timeout) {
1265 45
            curl_setopt($curl, CURLOPT_TIMEOUT, $timeout == 1 ? 1 : $timeout - 1);
1266
        }
1267 22
1268 22
        switch ($method) {
1269
            case 'http10':
1270
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
1271 22
                break;
1272 22
            case 'http11':
1273
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
1274
                break;
1275
            case 'h2c':
1276 22
                if (defined('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE')) {
1277 22
                    curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE);
1278 22
                } else {
1279 22
                    /// @todo make this a proper error, i.e. return a failure
1280
                    $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. HTTP2 is not supported by the current PHP/curl install');
1281
                }
1282 22
                break;
1283 22
            case 'h2':
1284 22
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
1285
                break;
1286
        }
1287
1288 22
        if ($username && $password) {
1289 22
            curl_setopt($curl, CURLOPT_USERPWD, $username . ':' . $password);
1290 22
            if (defined('CURLOPT_HTTPAUTH')) {
1291
                curl_setopt($curl, CURLOPT_HTTPAUTH, $authType);
1292 22
            } elseif ($authType != 1) {
1293 22
                /// @todo make this a proper error, i.e. return a failure
1294
                $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported by the current PHP/curl install');
1295
            }
1296 22
        }
1297 22
1298
        // note: h2c is http2 without the https. No need to have it in this IF
1299
        if ($method == 'https' || $method == 'h2') {
1300 22
            // set cert file
1301 22
            if ($cert) {
1302
                curl_setopt($curl, CURLOPT_SSLCERT, $cert);
1303
            }
1304
            // set cert password
1305
            if ($certPass) {
1306
                curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $certPass);
1307 22
            }
1308
            // whether to verify remote host's cert
1309
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verifypeer);
1310
            // set ca certificates file/dir
1311 24
            if ($caCert) {
1312 24
                curl_setopt($curl, CURLOPT_CAINFO, $caCert);
1313
            }
1314
            if ($caCertDir) {
1315 24
                curl_setopt($curl, CURLOPT_CAPATH, $caCertDir);
1316 24
            }
1317
            // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1318
            if ($key) {
1319
                curl_setopt($curl, CURLOPT_SSLKEY, $key);
1320 24
            }
1321 24
            // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1322 24
            if ($keyPass) {
1323 24
                curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $keyPass);
1324 24
            }
1325
            // whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that
1326
            // it matches the hostname used
1327
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $this->verifyhost);
1328 24
            // allow usage of different SSL versions
1329 24
            curl_setopt($curl, CURLOPT_SSLVERSION, $sslVersion);
1330 22
        }
1331 22
1332
        // proxy info
1333 22
        if ($proxyHost) {
1334
            if ($proxyPort == 0) {
1335
                $proxyPort = 8080; // NB: even for HTTPS, local connection is on port 8080
1336 22
            }
1337
            curl_setopt($curl, CURLOPT_PROXY, $proxyHost . ':' . $proxyPort);
1338 22
            if ($proxyUsername) {
1339
                curl_setopt($curl, CURLOPT_PROXYUSERPWD, $proxyUsername . ':' . $proxyPassword);
1340
                if (defined('CURLOPT_PROXYAUTH')) {
1341 22
                    curl_setopt($curl, CURLOPT_PROXYAUTH, $proxyAuthType);
1342 22
                } elseif ($proxyAuthType != 1) {
1343
                    /// @todo make this a proper error, i.e. return a failure
1344
                    $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported by the current PHP/curl install');
1345
                }
1346
            }
1347
        }
1348 24
1349
        // NB: should we build cookie http headers by hand rather than let CURL do it?
1350
        // the following code does not honour 'expires', 'path' and 'domain' cookie attributes set to client obj the the user...
1351
        if (count($this->cookies)) {
1352
            $cookieHeader = '';
1353
            foreach ($this->cookies as $name => $cookie) {
1354
                $cookieHeader .= $name . '=' . $cookie['value'] . '; ';
1355
            }
1356
            curl_setopt($curl, CURLOPT_COOKIE, substr($cookieHeader, 0, -2));
1357
        }
1358
1359
        foreach ($this->extracurlopts as $opt => $val) {
1360
            curl_setopt($curl, $opt, $val);
1361
        }
1362
1363
        if ($this->debug > 1) {
1364
            $this->getLogger()->debug("---SENDING---\n$payload\n---END---");
1365
        }
1366
1367
        return $curl;
1368
    }
1369
1370
    /**
1371
     * Send an array of requests and return an array of responses.
1372
     *
1373
     * Unless $this->no_multicall has been set to true, it will try first to use one single xml-rpc call to server method
1374
     * system.multicall, and revert to sending many successive calls in case of failure.
1375
     * This failure is also stored in $this->no_multicall for subsequent calls.
1376
     * Unfortunately, there is no server error code universally used to denote the fact that multicall is unsupported,
1377
     * so there is no way to reliably distinguish between that and a temporary failure.
1378
     * If you are sure that server supports multicall and do not want to fallback to using many single calls, set the
1379
     * fourth parameter to FALSE.
1380
     *
1381
     * NB: trying to shoehorn extra functionality into existing syntax has resulted
1382
     * in pretty much convoluted code...
1383
     *
1384
     * @param Request[] $reqs an array of Request objects
1385
     * @param integer $timeout deprecated - connection timeout (in seconds). See the details in the docs for the send() method
1386
     * @param string $method deprecated - the http protocol variant to be used. See the details in the docs for the send() method
1387
     * @param boolean $fallback When true, upon receiving an error during multicall, multiple single calls will be
1388
     *                         attempted
1389
     * @return Response[]
1390
     */
1391
    public function multicall($reqs, $timeout = 0, $method = '', $fallback = true)
1392
    {
1393
        if ($method == '') {
1394
            $method = $this->method;
1395
        }
1396
1397
        if (!$this->no_multicall) {
1398
            $results = $this->_try_multicall($reqs, $timeout, $method);
1399
            /// @todo how to handle the case of $this->return_type = xml?
1400
            if (is_array($results)) {
1401
                // System.multicall succeeded
1402
                return $results;
1403
            } else {
1404
                // either system.multicall is unsupported by server, or the call failed for some other reason.
1405
                // Feature creep: is there a way to tell apart unsupported multicall from other faults?
1406
                if ($fallback) {
1407
                    // Don't try it next time...
1408
                    $this->no_multicall = true;
1409
                } else {
1410
                    $result = $results;
1411
                }
1412
            }
1413
        } else {
1414
            // override fallback, in case careless user tries to do two
1415
            // opposite things at the same time
1416
            $fallback = true;
1417
        }
1418
1419
        $results = array();
1420
        if ($fallback) {
1421
            // system.multicall is (probably) unsupported by server: emulate multicall via multiple requests
1422
            /// @todo use curl multi_ functions to make this quicker (see the implementation in the parallel.php demo)
1423
            foreach ($reqs as $req) {
1424
                $results[] = $this->send($req, $timeout, $method);
1425
            }
1426
        } else {
1427
            // user does NOT want to fallback on many single calls: since we should always return an array of responses,
1428
            // we return an array with the same error repeated n times
1429
            foreach ($reqs as $req) {
1430
                $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...
1431
            }
1432
        }
1433
1434
        return $results;
1435
    }
1436
1437
    /**
1438
     * Attempt to boxcar $reqs via system.multicall.
1439
     *
1440
     * @param Request[] $reqs
1441
     * @param int $timeout
1442
     * @param string $method
1443
     * @return Response[]|Response a single Response when the call returned a fault / does not conform to what we expect
1444
     *                             from a multicall response
1445
     */
1446
    private function _try_multicall($reqs, $timeout, $method)
1447
    {
1448
        // Construct multicall request
1449
        $calls = array();
1450
        foreach ($reqs as $req) {
1451
            $call['methodName'] = new Value($req->method(), 'string');
1452
            $numParams = $req->getNumParams();
1453
            $params = array();
1454
            for ($i = 0; $i < $numParams; $i++) {
1455
                $params[$i] = $req->getParam($i);
1456
            }
1457
            $call['params'] = new Value($params, 'array');
1458
            $calls[] = new Value($call, 'struct');
1459
        }
1460
        $multiCall = new Request('system.multicall');
1461
        $multiCall->addParam(new Value($calls, 'array'));
1462
1463
        // Attempt RPC call
1464
        $result = $this->send($multiCall, $timeout, $method);
1465
1466
        if ($result->faultCode() != 0) {
1467
            // call to system.multicall failed
1468
            return $result;
1469
        }
1470
1471
        // Unpack responses.
1472
        $rets = $result->value();
1473
        $response = array();
1474
1475
        if ($this->return_type == 'xml') {
1476
            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...
1477
                $response[] = new Response($rets, 0, '', 'xml', $result->httpResponse());
1478
            }
1479
1480
        } elseif ($this->return_type == 'phpvals') {
1481
            if (!is_array($rets)) {
0 ignored issues
show
introduced by
The condition is_array($rets) is always false.
Loading history...
1482
                // bad return type from system.multicall
1483
                return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1484
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': not an array', 'phpvals', $result->httpResponse());
1485
            }
1486
            $numRets = count($rets);
1487
            if ($numRets != count($reqs)) {
1488
                // wrong number of return values.
1489
                return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1490
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': incorrect number of responses', 'phpvals',
1491
                    $result->httpResponse());
1492
            }
1493
1494
            for ($i = 0; $i < $numRets; $i++) {
1495
                $val = $rets[$i];
1496
                if (!is_array($val)) {
1497
                    return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1498
                        PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array or struct",
1499
                        'phpvals', $result->httpResponse());
1500
                }
1501
                switch (count($val)) {
1502
                    case 1:
1503
                        if (!isset($val[0])) {
1504
                            // Bad value
1505
                            return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1506
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has no value",
1507
                                'phpvals', $result->httpResponse());
1508
                        }
1509
                        // Normal return value
1510
                        $response[$i] = new Response($val[0], 0, '', 'phpvals', $result->httpResponse());
1511
                        break;
1512
                    case 2:
1513
                        /// @todo remove usage of @: it is apparently quite slow
1514
                        $code = @$val['faultCode'];
1515
                        if (!is_int($code)) {
1516
                            /// @todo should we check that it is != 0?
1517
                            return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1518
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1519
                                'phpvals', $result->httpResponse());
1520
                        }
1521
                        $str = @$val['faultString'];
1522
                        if (!is_string($str)) {
1523
                            return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1524
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no FaultString",
1525
                                'phpvals', $result->httpResponse());
1526
                        }
1527
                        $response[$i] = new Response(0, $code, $str, 'phpvals', $result->httpResponse());
1528
                        break;
1529
                    default:
1530
                        return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1531
                            PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1532
                            'phpvals', $result->httpResponse());
1533
                }
1534
            }
1535
1536
        } else {
1537
            // return type == 'xmlrpcvals'
1538
            if ($rets->kindOf() != 'array') {
0 ignored issues
show
Bug introduced by
The method kindOf() does not exist on integer. ( Ignorable by Annotation )

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

1538
            if ($rets->/** @scrutinizer ignore-call */ kindOf() != 'array') {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1539
                return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1540
                    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 1450. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
1541
                    $result->httpResponse());
1542
            }
1543
            $numRets = $rets->count();
1544
            if ($numRets != count($reqs)) {
1545
                // wrong number of return values.
1546
                return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1547
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': incorrect number of responses', 'xmlrpcvals',
1548
                    $result->httpResponse());
1549
            }
1550
1551
            foreach ($rets as $i => $val) {
0 ignored issues
show
Bug introduced by
The expression $rets of type integer is not traversable.
Loading history...
1552
                switch ($val->kindOf()) {
1553
                    case 'array':
1554
                        if ($val->count() != 1) {
1555
                            return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1556
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1557
                                'phpvals', $result->httpResponse());
1558
                        }
1559
                        // Normal return value
1560
                        $response[] = new Response($val[0], 0, '', 'xmlrpcvals', $result->httpResponse());
1561
                        break;
1562
                    case 'struct':
1563
                        if ($val->count() != 2) {
1564
                            return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1565
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1566
                                'phpvals', $result->httpResponse());
1567
                        }
1568
                        /** @var Value $code */
1569
                        $code = $val['faultCode'];
1570
                        if ($code->kindOf() != 'scalar' || $code->scalartyp() != 'int') {
1571
                            return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1572
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1573
                                'xmlrpcvals', $result->httpResponse());
1574
                        }
1575
                        /** @var Value $str */
1576
                        $str = $val['faultString'];
1577
                        if ($str->kindOf() != 'scalar' || $str->scalartyp() != 'string') {
1578
                            return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1579
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1580
                                'xmlrpcvals', $result->httpResponse());
1581
                        }
1582
                        $response[] = new Response(0, $code->scalarval(), $str->scalarval(), 'xmlrpcvals', $result->httpResponse());
1583
                        break;
1584
                    default:
1585
                        return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1586
                            PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array or struct",
1587
                            'xmlrpcvals', $result->httpResponse());
1588
                }
1589
            }
1590
        }
1591
1592
        return $response;
1593
    }
1594
}
1595