Passed
Push — master ( c58543...7e23e7 )
by Gaetano
13:15
created

Client::_try_multicall()   D

Complexity

Conditions 25
Paths 63

Size

Total Lines 114
Code Lines 74

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 57
CRAP Score 30.6486

Importance

Changes 0
Metric Value
cc 25
eloc 74
c 0
b 0
f 0
nc 63
nop 3
dl 0
loc 114
rs 4.1666
ccs 57
cts 72
cp 0.7917
crap 30.6486

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

653
        $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...
654
        $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

654
        /** @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...
655
    {
656 374
        if ($port == 0) {
657 372
            $port = ( $method === 'https' ) ? 443 : 80;
658
        }
659
660
        // Only create the payload if it was not created previously
661 374
        if (empty($req->payload)) {
662 345
            $req->serialize($this->request_charset_encoding);
663
        }
664
665 374
        $payload = $req->payload;
666
        // Deflate request body and set appropriate request headers
667 374
        $encodingHdr = '';
668 374
        if (function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate')) {
669 64
            if ($this->request_compression == 'gzip') {
670 32
                $a = @gzencode($payload);
671 32
                if ($a) {
672 32
                    $payload = $a;
673 32
                    $encodingHdr = "Content-Encoding: gzip\r\n";
674
                }
675
            } else {
676 32
                $a = @gzcompress($payload);
677 32
                if ($a) {
678 32
                    $payload = $a;
679 32
                    $encodingHdr = "Content-Encoding: deflate\r\n";
680
                }
681
            }
682
        }
683
684
        // thanks to Grant Rauscher <[email protected]> for this
685 374
        $credentials = '';
686 374
        if ($username != '') {
687 32
            $credentials = 'Authorization: Basic ' . base64_encode($username . ':' . $password) . "\r\n";
688 32
            if ($authType != 1) {
689
                $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported with HTTP 1.0');
690
            }
691
        }
692
693 374
        $acceptedEncoding = '';
694 374
        if (is_array($this->accepted_compression) && count($this->accepted_compression)) {
695 72
            $acceptedEncoding = 'Accept-Encoding: ' . implode(', ', $this->accepted_compression) . "\r\n";
696
        }
697
698 374
        $proxyCredentials = '';
699 374
        if ($proxyHost) {
700 32
            if ($proxyPort == 0) {
701
                $proxyPort = 8080;
702
            }
703 32
            $connectServer = $proxyHost;
704 32
            $connectPort = $proxyPort;
705 32
            $transport = 'tcp';
706 32
            $uri = 'http://' . $server . ':' . $port . $this->path;
707 32
            if ($proxyUsername != '') {
708
                if ($proxyAuthType != 1) {
709
                    $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported with HTTP 1.0');
710
                }
711 32
                $proxyCredentials = 'Proxy-Authorization: Basic ' . base64_encode($proxyUsername . ':' . $proxyPassword) . "\r\n";
712
            }
713
        } else {
714 342
            $connectServer = $server;
715 342
            $connectPort = $port;
716 342
            $transport = ( $method === 'https' ) ? 'tls' : 'tcp';
717 342
            $uri = $this->path;
718
        }
719
720
        // Cookie generation, as per rfc2965 (version 1 cookies) or netscape's rules (version 0 cookies)
721 374
        $cookieHeader = '';
722 374
        if (count($this->cookies)) {
723 309
            $version = '';
724 309
            foreach ($this->cookies as $name => $cookie) {
725 309
                if ($cookie['version']) {
726
                    $version = ' $Version="' . $cookie['version'] . '";';
727
                    $cookieHeader .= ' ' . $name . '="' . $cookie['value'] . '";';
728
                    if ($cookie['path']) {
729
                        $cookieHeader .= ' $Path="' . $cookie['path'] . '";';
730
                    }
731
                    if ($cookie['domain']) {
732
                        $cookieHeader .= ' $Domain="' . $cookie['domain'] . '";';
733
                    }
734
                    if ($cookie['port']) {
735
                        $cookieHeader .= ' $Port="' . $cookie['port'] . '";';
736
                    }
737
                } else {
738 309
                    $cookieHeader .= ' ' . $name . '=' . $cookie['value'] . ";";
739
                }
740
            }
741 309
            $cookieHeader = 'Cookie:' . $version . substr($cookieHeader, 0, -1) . "\r\n";
742
        }
743
744
        // omit port if default
745 374
        if (($port == 80 && in_array($method, array('http', 'http10'))) || ($port == 443 && $method == 'https')) {
746 374
            $port =  '';
747
        } else {
748
            $port = ':' . $port;
749
        }
750
751 374
        $op = 'POST ' . $uri . " HTTP/1.0\r\n" .
752 374
            'User-Agent: ' . $this->user_agent . "\r\n" .
753 374
            'Host: ' . $server . $port . "\r\n" .
754 374
            $credentials .
755 374
            $proxyCredentials .
756 374
            $acceptedEncoding .
757 374
            $encodingHdr .
758 374
            'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings) . "\r\n" .
759 374
            $cookieHeader .
760 374
            'Content-Type: ' . $req->content_type . "\r\nContent-Length: " .
761 374
            strlen($payload) . "\r\n\r\n" .
762 374
            $payload;
763
764 374
        if ($this->debug > 1) {
765 2
            $this->getLogger()->debugMessage("---SENDING---\n$op\n---END---");
766
        }
767
768 374
        $contextOptions = array();
769 374
        if ($method == 'https') {
770 32
            if ($cert != '') {
771
                $contextOptions['ssl']['local_cert'] = $cert;
772
                if ($certPass != '') {
773
                    $contextOptions['ssl']['passphrase'] = $certPass;
774
                }
775
            }
776 32
            if ($caCert != '') {
777
                $contextOptions['ssl']['cafile'] = $caCert;
778
            }
779 32
            if ($caCertDir != '') {
780
                $contextOptions['ssl']['capath'] = $caCertDir;
781
            }
782 32
            if ($key != '') {
783
                $contextOptions['ssl']['local_pk'] = $key;
784
            }
785 32
            $contextOptions['ssl']['verify_peer'] = $this->verifypeer;
786 32
            $contextOptions['ssl']['verify_peer_name'] = $this->verifypeer;
787
        }
788
789 374
        $context = stream_context_create($contextOptions);
790
791 374
        if ($timeout <= 0) {
792 63
            $connectTimeout = ini_get('default_socket_timeout');
793
        } else {
794 311
            $connectTimeout = $timeout;
795
        }
796
797 374
        $this->errno = 0;
798 374
        $this->errstr = '';
799
800 374
        $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr, $connectTimeout,
0 ignored issues
show
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

800
        $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr, /** @scrutinizer ignore-type */ $connectTimeout,
Loading history...
801 374
            STREAM_CLIENT_CONNECT, $context);
802 374
        if ($fp) {
0 ignored issues
show
introduced by
$fp is of type false|resource, thus it always evaluated to false.
Loading history...
803 373
            if ($timeout > 0) {
804 367
                stream_set_timeout($fp, $timeout);
805
            }
806
        } else {
807 1
            if ($this->errstr == '') {
808
                $err = error_get_last();
809
                $this->errstr = $err['message'];
810
            }
811
812 1
            $this->errstr = 'Connect error: ' . $this->errstr;
813 1
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr . ' (' . $this->errno . ')');
814
815 1
            return $r;
816
        }
817
818 373
        if (!fputs($fp, $op, strlen($op))) {
819
            fclose($fp);
820
            $this->errstr = 'Write error';
821
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr);
822
823
            return $r;
824
        }
825
826
        // Close socket before parsing.
827
        // It should yield slightly better execution times, and make easier recursive calls (e.g. to follow http redirects)
828 373
        $ipd = '';
829
        do {
830
            // shall we check for $data === FALSE?
831
            // as per the manual, it signals an error
832 373
            $ipd .= fread($fp, 32768);
833 373
        } while (!feof($fp));
834 373
        fclose($fp);
835
836 373
        $r = $req->parseResponse($ipd, false, $this->return_type);
837
838 373
        return $r;
839
    }
840
841
    /**
842
     * Contributed by Justin Miller <[email protected]>
843
     * Requires curl to be built into PHP
844
     * NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers!
845
     *
846
     * @param Request $req
847
     * @param string $server
848
     * @param int $port
849
     * @param int $timeout
850
     * @param string $username
851
     * @param string $password
852
     * @param int $authType
853
     * @param string $cert
854
     * @param string $certPass
855
     * @param string $caCert
856
     * @param string $caCertDir
857
     * @param string $proxyHost
858
     * @param int $proxyPort
859
     * @param string $proxyUsername
860
     * @param string $proxyPassword
861
     * @param int $proxyAuthType
862
     * @param string $method 'http' (let curl decide), 'http10', 'http11', 'https', 'http2' or 'http2tls'
863
     * @param bool $keepAlive
864
     * @param string $key
865
     * @param string $keyPass
866
     * @param int $sslVersion
867
     * @return Response
868
     */
869 322
    protected function sendPayloadCURL($req, $server, $port, $timeout = 0, $username = '', $password = '',
870
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
871
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'https', $keepAlive = false, $key = '',
872
        $keyPass = '', $sslVersion = 0)
873
    {
874 322
        if (!function_exists('curl_init')) {
875
            $this->errstr = 'CURL unavailable on this install';
876
            return new Response(0, PhpXmlRpc::$xmlrpcerr['no_curl'], PhpXmlRpc::$xmlrpcstr['no_curl']);
877
        }
878 322
        if ($method == 'https') {
879 64
            if (($info = curl_version()) &&
880 64
                ((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...
881
            ) {
882
                $this->errstr = 'SSL unavailable on this install';
883 64
                return new Response(0, PhpXmlRpc::$xmlrpcerr['no_ssl'], PhpXmlRpc::$xmlrpcstr['no_ssl']);
884
            }
885 258
        } elseif (($method == 'http2' || $method == 'http2tls') && !defined('CURL_HTTP_VERSION_2')) {
886
            $this->errstr = 'HTTP/2 unavailable on this install';
887
            return new Response(0, PhpXmlRpc::$xmlrpcerr['no_http2'], PhpXmlRpc::$xmlrpcstr['no_http2']);
888
        }
889
890 322
        if ($port == 0) {
891 321
            if (in_array($method, array('http', 'http10', 'http11', 'http2'))) {
892 225
                $port = 80;
893
            } else {
894 96
                $port = 443;
895
            }
896
        }
897
898
        // Only create the payload if it was not created previously
899 322
        if (empty($req->payload)) {
900 302
            $req->serialize($this->request_charset_encoding);
901
        }
902
903
        // Deflate request body and set appropriate request headers
904 322
        $payload = $req->payload;
905 322
        if (function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate')) {
906 64
            if ($this->request_compression == 'gzip') {
907 32
                $a = @gzencode($payload);
908 32
                if ($a) {
909 32
                    $payload = $a;
910 32
                    $encodingHdr = 'Content-Encoding: gzip';
911
                }
912
            } else {
913 32
                $a = @gzcompress($payload);
914 32
                if ($a) {
915 32
                    $payload = $a;
916 64
                    $encodingHdr = 'Content-Encoding: deflate';
917
                }
918
            }
919
        } else {
920 258
            $encodingHdr = '';
921
        }
922
923 322
        if ($this->debug > 1) {
924
            $this->getLogger()->debugMessage("---SENDING---\n$payload\n---END---");
925
        }
926
927 322
        if (!$keepAlive || !$this->xmlrpc_curl_handle) {
928 322
            if ($method == 'http11' || $method == 'http10' || $method == 'http2') {
929 64
                $protocol = 'http';
930
            } else {
931 258
                if ($method == 'http2tls') {
932 32
                    $protocol = 'https';
933
                } else {
934 226
                    $protocol = $method;
935
                }
936
            }
937 322
            $curl = curl_init($protocol . '://' . $server . ':' . $port . $this->path);
938 322
            if ($keepAlive) {
939 322
                $this->xmlrpc_curl_handle = $curl;
940
            }
941
        } else {
942 56
            $curl = $this->xmlrpc_curl_handle;
943
        }
944
945
        // results into variable
946 322
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
947
948 322
        if ($this->debug > 1) {
949
            curl_setopt($curl, CURLOPT_VERBOSE, true);
950
            /// @todo allow callers to redirect curlopt_stderr to some stream which can be buffered
951
        }
952 322
        curl_setopt($curl, CURLOPT_USERAGENT, $this->user_agent);
953
        // required for XMLRPC: post the data
954 322
        curl_setopt($curl, CURLOPT_POST, 1);
955
        // the data
956 322
        curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
957
958
        // return the header too
959 322
        curl_setopt($curl, CURLOPT_HEADER, 1);
960
961
        // NB: if we set an empty string, CURL will add http header indicating
962
        // ALL methods it is supporting. This is possibly a better option than letting the user tell what curl can / cannot do...
963 322
        if (is_array($this->accepted_compression) && count($this->accepted_compression)) {
964
            //curl_setopt($curl, CURLOPT_ENCODING, implode(',', $this->accepted_compression));
965
            // empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
966 65
            if (count($this->accepted_compression) == 1) {
967 64
                curl_setopt($curl, CURLOPT_ENCODING, $this->accepted_compression[0]);
968
            } else {
969 1
                curl_setopt($curl, CURLOPT_ENCODING, '');
970
            }
971
        }
972
        // extra headers
973 322
        $headers = array('Content-Type: ' . $req->content_type, 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings));
974
        // if no keepalive is wanted, let the server know it in advance
975 322
        if (!$keepAlive) {
976 160
            $headers[] = 'Connection: close';
977
        }
978
        // request compression header
979 322
        if ($encodingHdr) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $encodingHdr does not seem to be defined for all execution paths leading up to this point.
Loading history...
980 64
            $headers[] = $encodingHdr;
981
        }
982
983
        // Fix the HTTP/1.1 417 Expectation Failed Bug (curl by default adds a 'Expect: 100-continue' header when POST
984
        // size exceeds 1025 bytes, apparently)
985 322
        $headers[] = 'Expect:';
986
987 322
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
988
        // timeout is borked
989 322
        if ($timeout) {
990 272
            curl_setopt($curl, CURLOPT_TIMEOUT, $timeout == 1 ? 1 : $timeout - 1);
991
        }
992
993 322
        switch($method) {
994 322
            case 'http10':
995 32
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
996 32
                break;
997 290
            case 'http11':
998
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
999
                break;
1000 290
            case 'http2':
1001 32
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2);
1002 32
                break;
1003 258
            case 'http2tls':
1004 32
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS);
1005 32
                break;
1006
        }
1007
1008 322
        if ($username && $password) {
1009 32
            curl_setopt($curl, CURLOPT_USERPWD, $username . ':' . $password);
1010 32
            if (defined('CURLOPT_HTTPAUTH')) {
1011 32
                curl_setopt($curl, CURLOPT_HTTPAUTH, $authType);
1012
            } elseif ($authType != 1) {
1013
                $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported by the current PHP/curl install');
1014
            }
1015
        }
1016
1017 322
        if ($method == 'https' || $method == 'http2tls') {
1018
            // set cert file
1019 96
            if ($cert) {
1020
                curl_setopt($curl, CURLOPT_SSLCERT, $cert);
1021
            }
1022
            // set cert password
1023 96
            if ($certPass) {
1024
                curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $certPass);
1025
            }
1026
            // whether to verify remote host's cert
1027 96
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verifypeer);
1028
            // set ca certificates file/dir
1029 96
            if ($caCert) {
1030
                curl_setopt($curl, CURLOPT_CAINFO, $caCert);
1031
            }
1032 96
            if ($caCertDir) {
1033
                curl_setopt($curl, CURLOPT_CAPATH, $caCertDir);
1034
            }
1035
            // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1036 96
            if ($key) {
1037
                curl_setopt($curl, CURLOPT_SSLKEY, $key);
1038
            }
1039
            // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1040 96
            if ($keyPass) {
1041
                curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $keyPass);
1042
            }
1043
            // whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that
1044
            // it matches the hostname used
1045 96
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $this->verifyhost);
1046
            // allow usage of different SSL versions
1047 96
            curl_setopt($curl, CURLOPT_SSLVERSION, $sslVersion);
1048
        }
1049
1050
        // proxy info
1051 322
        if ($proxyHost) {
1052 64
            if ($proxyPort == 0) {
1053
                $proxyPort = 8080; // NB: even for HTTPS, local connection is on port 8080
1054
            }
1055 64
            curl_setopt($curl, CURLOPT_PROXY, $proxyHost . ':' . $proxyPort);
1056 64
            if ($proxyUsername) {
1057
                curl_setopt($curl, CURLOPT_PROXYUSERPWD, $proxyUsername . ':' . $proxyPassword);
1058
                if (defined('CURLOPT_PROXYAUTH')) {
1059
                    curl_setopt($curl, CURLOPT_PROXYAUTH, $proxyAuthType);
1060
                } elseif ($proxyAuthType != 1) {
1061
                    $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported by the current PHP/curl install');
1062
                }
1063
            }
1064
        }
1065
1066
        // NB: should we build cookie http headers by hand rather than let CURL do it?
1067
        // the following code does not honour 'expires', 'path' and 'domain' cookie attributes set to client obj the the user...
1068 322
        if (count($this->cookies)) {
1069 271
            $cookieHeader = '';
1070 271
            foreach ($this->cookies as $name => $cookie) {
1071 271
                $cookieHeader .= $name . '=' . $cookie['value'] . '; ';
1072
            }
1073 271
            curl_setopt($curl, CURLOPT_COOKIE, substr($cookieHeader, 0, -2));
1074
        }
1075
1076 322
        foreach ($this->extracurlopts as $opt => $val) {
1077
            curl_setopt($curl, $opt, $val);
1078
        }
1079
1080 322
        $result = curl_exec($curl);
1081
1082 322
        if ($this->debug > 1) {
1083
            $message = "---CURL INFO---\n";
1084
            foreach (curl_getinfo($curl) as $name => $val) {
1085
                if (is_array($val)) {
1086
                    $val = implode("\n", $val);
1087
                }
1088
                $message .= $name . ': ' . $val . "\n";
1089
            }
1090
            $message .= '---END---';
1091
            $this->getLogger()->debugMessage($message);
1092
        }
1093
1094 322
        if (!$result) {
1095
            /// @todo we should use a better check here - what if we get back '' or '0'?
1096
1097 1
            $this->errstr = 'no response';
1098 1
            $resp = new Response(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] . ': ' . curl_error($curl));
1099 1
            curl_close($curl);
1100 1
            if ($keepAlive) {
1101 1
                $this->xmlrpc_curl_handle = null;
1102
            }
1103
        } else {
1104 322
            if (!$keepAlive) {
1105 160
                curl_close($curl);
1106
            }
1107 322
            $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

1107
            $resp = $req->parseResponse(/** @scrutinizer ignore-type */ $result, true, $this->return_type);
Loading history...
1108
            // if we got back a 302, we can not reuse the curl handle for later calls
1109 322
            if ($resp->faultCode() == PhpXmlRpc::$xmlrpcerr['http_error'] && $keepAlive) {
1110
                curl_close($curl);
1111
                $this->xmlrpc_curl_handle = null;
1112
            }
1113
        }
1114
1115 322
        return $resp;
1116
    }
1117
1118
    /**
1119
     * Send an array of requests and return an array of responses.
1120
     *
1121
     * Unless $this->no_multicall has been set to true, it will try first to use one single xmlrpc call to server method
1122
     * system.multicall, and revert to sending many successive calls in case of failure.
1123
     * This failure is also stored in $this->no_multicall for subsequent calls.
1124
     * Unfortunately, there is no server error code universally used to denote the fact that multicall is unsupported,
1125
     * so there is no way to reliably distinguish between that and a temporary failure.
1126
     * If you are sure that server supports multicall and do not want to fallback to using many single calls, set the
1127
     * fourth parameter to FALSE.
1128
     *
1129
     * NB: trying to shoehorn extra functionality into existing syntax has resulted
1130
     * in pretty much convoluted code...
1131
     *
1132
     * @param Request[] $reqs an array of Request objects
1133
     * @param integer $timeout connection timeout (in seconds). See the details in the docs for the send() method
1134
     * @param string $method the http protocol variant to be used. See the details in the docs for the send() method
1135
     * @param boolean fallback When true, upon receiving an error during multicall, multiple single calls will be
0 ignored issues
show
Bug introduced by
The type PhpXmlRpc\fallback was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
1136
     *                         attempted
1137
     *
1138
     * @return Response[]
1139
     */
1140 65
    public function multicall($reqs, $timeout = 0, $method = '', $fallback = true)
1141
    {
1142 65
        if ($method == '') {
1143
            $method = $this->method;
1144
        }
1145 65
        if (!$this->no_multicall) {
1146 44
            $results = $this->_try_multicall($reqs, $timeout, $method);
1147 44
            if (is_array($results)) {
1148
                // System.multicall succeeded
1149 44
                return $results;
1150
            } else {
1151
                // either system.multicall is unsupported by server,
1152
                // or call failed for some other reason.
1153
                if ($fallback) {
1154
                    // Don't try it next time...
1155
                    $this->no_multicall = true;
1156
                } else {
1157
                    if (is_a($results, '\PhpXmlRpc\Response')) {
1158
                        $result = $results;
1159
                    } else {
1160
                        $result = new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'], PhpXmlRpc::$xmlrpcstr['multicall_error']);
1161
                    }
1162
                }
1163
            }
1164
        } else {
1165
            // override fallback, in case careless user tries to do two
1166
            // opposite things at the same time
1167 22
            $fallback = true;
1168
        }
1169
1170 22
        $results = array();
1171 22
        if ($fallback) {
1172
            // system.multicall is (probably) unsupported by server:
1173
            // emulate multicall via multiple requests
1174 22
            foreach ($reqs as $req) {
1175 22
                $results[] = $this->send($req, $timeout, $method);
1176
            }
1177
        } else {
1178
            // user does NOT want to fallback on many single calls:
1179
            // since we should always return an array of responses,
1180
            // return an array with the same error repeated n times
1181
            foreach ($reqs as $req) {
1182
                $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...
1183
            }
1184
        }
1185
1186 22
        return $results;
1187
    }
1188
1189
    /**
1190
     * Attempt to boxcar $reqs via system.multicall.
1191
     *
1192
     * Returns either an array of Response, a single error Response or false (when received response does not respect
1193
     * valid multicall syntax).
1194
     *
1195
     * @param Request[] $reqs
1196
     * @param int $timeout
1197
     * @param string $method
1198
     * @return Response[]|bool|mixed|Response
1199
     */
1200 44
    private function _try_multicall($reqs, $timeout, $method)
1201
    {
1202
        // Construct multicall request
1203 44
        $calls = array();
1204 44
        foreach ($reqs as $req) {
1205 44
            $call['methodName'] = new Value($req->method(), 'string');
1206 44
            $numParams = $req->getNumParams();
1207 44
            $params = array();
1208 44
            for ($i = 0; $i < $numParams; $i++) {
1209 44
                $params[$i] = $req->getParam($i);
1210
            }
1211 44
            $call['params'] = new Value($params, 'array');
1212 44
            $calls[] = new Value($call, 'struct');
1213
        }
1214 44
        $multiCall = new Request('system.multicall');
1215 44
        $multiCall->addParam(new Value($calls, 'array'));
1216
1217
        // Attempt RPC call
1218 44
        $result = $this->send($multiCall, $timeout, $method);
1219
1220 44
        if ($result->faultCode() != 0) {
1221
            // call to system.multicall failed
1222
            return $result;
1223
        }
1224
1225
        // Unpack responses.
1226 44
        $rets = $result->value();
1227
1228 44
        if ($this->return_type == 'xml') {
1229
            return $rets;
1230 44
        } elseif ($this->return_type == 'phpvals') {
1231
            /// @todo test this code branch...
1232 22
            $rets = $result->value();
1233 22
            if (!is_array($rets)) {
0 ignored issues
show
introduced by
The condition is_array($rets) is always false.
Loading history...
1234
                return false;       // bad return type from system.multicall
1235
            }
1236 22
            $numRets = count($rets);
1237 22
            if ($numRets != count($reqs)) {
1238
                return false;       // wrong number of return values.
1239
            }
1240
1241 22
            $response = array();
1242 22
            for ($i = 0; $i < $numRets; $i++) {
1243 22
                $val = $rets[$i];
1244 22
                if (!is_array($val)) {
1245
                    return false;
1246
                }
1247 22
                switch (count($val)) {
1248 22
                    case 1:
1249 22
                        if (!isset($val[0])) {
1250
                            return false;       // Bad value
1251
                        }
1252
                        // Normal return value
1253 22
                        $response[$i] = new Response($val[0], 0, '', 'phpvals');
1254 22
                        break;
1255 22
                    case 2:
1256
                        /// @todo remove usage of @: it is apparently quite slow
1257 22
                        $code = @$val['faultCode'];
1258 22
                        if (!is_int($code)) {
1259
                            return false;
1260
                        }
1261 22
                        $str = @$val['faultString'];
1262 22
                        if (!is_string($str)) {
1263
                            return false;
1264
                        }
1265 22
                        $response[$i] = new Response(0, $code, $str);
1266 22
                        break;
1267
                    default:
1268
                        return false;
1269
                }
1270
            }
1271
1272 22
            return $response;
1273
        } else {
1274
            // return type == 'xmlrpcvals'
1275
1276 23
            $rets = $result->value();
1277 23
            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

1277
            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...
1278
                return false;       // bad return type from system.multicall
1279
            }
1280 23
            $numRets = $rets->count();
1281 23
            if ($numRets != count($reqs)) {
1282
                return false;       // wrong number of return values.
1283
            }
1284
1285 23
            $response = array();
1286 23
            foreach($rets as $val) {
0 ignored issues
show
Bug introduced by
The expression $rets of type integer is not traversable.
Loading history...
1287 23
                switch ($val->kindOf()) {
1288 23
                    case 'array':
1289 23
                        if ($val->count() != 1) {
1290
                            return false;       // Bad value
1291
                        }
1292
                        // Normal return value
1293 23
                        $response[] = new Response($val[0]);
1294 23
                        break;
1295 22
                    case 'struct':
1296 22
                        $code = $val['faultCode'];
1297
                        /** @var Value $code */
1298 22
                        if ($code->kindOf() != 'scalar' || $code->scalartyp() != 'int') {
1299
                            return false;
1300
                        }
1301 22
                        $str = $val['faultString'];
1302
                        /** @var Value $str */
1303 22
                        if ($str->kindOf() != 'scalar' || $str->scalartyp() != 'string') {
1304
                            return false;
1305
                        }
1306 22
                        $response[] = new Response(0, $code->scalarval(), $str->scalarval());
1307 22
                        break;
1308
                    default:
1309
                        return false;
1310
                }
1311
            }
1312
1313 23
            return $response;
1314
        }
1315
    }
1316
}
1317