Completed
Push — master ( 87b7a4...6ce28d )
by Gaetano
11:11 queued 06:38
created

Client::sendPayloadSocket()   F

Complexity

Conditions 42
Paths > 20000

Size

Total Lines 186
Code Lines 121

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 89
CRAP Score 62.3892

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 42
eloc 121
c 3
b 0
f 0
nc 3456000
nop 20
dl 0
loc 186
rs 0
ccs 89
cts 115
cp 0.7739
crap 62.3892

How to fix   Long Method    Complexity    Many Parameters   

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:

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

636
        $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...
637
        $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

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

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

1072
            $resp = $req->parseResponse(/** @scrutinizer ignore-type */ $result, true, $this->return_type);
Loading history...
1073
            // if we got back a 302, we can not reuse the curl handle for later calls
1074 242
            if ($resp->faultCode() == PhpXmlRpc::$xmlrpcerr['http_error'] && $keepAlive) {
1075
                curl_close($curl);
1076
                $this->xmlrpc_curl_handle = null;
1077
            }
1078
        }
1079
1080 242
        return $resp;
1081
    }
1082
1083
    /**
1084
     * Send an array of requests and return an array of responses.
1085
     *
1086
     * Unless $this->no_multicall has been set to true, it will try first to use one single xmlrpc call to server method
1087
     * system.multicall, and revert to sending many successive calls in case of failure.
1088
     * This failure is also stored in $this->no_multicall for subsequent calls.
1089
     * Unfortunately, there is no server error code universally used to denote the fact that multicall is unsupported,
1090
     * so there is no way to reliably distinguish between that and a temporary failure.
1091
     * If you are sure that server supports multicall and do not want to fallback to using many single calls, set the
1092
     * fourth parameter to FALSE.
1093
     *
1094
     * NB: trying to shoehorn extra functionality into existing syntax has resulted
1095
     * in pretty much convoluted code...
1096
     *
1097
     * @param Request[] $reqs an array of Request objects
1098
     * @param integer $timeout connection timeout (in seconds). See the details in the docs for the send() method
1099
     * @param string $method the http protocol variant to be used. See the details in the docs for the send() method
1100
     * @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...
1101
     *                         attempted
1102
     *
1103
     * @return Response[]
1104
     */
1105 58
    public function multicall($reqs, $timeout = 0, $method = '', $fallback = true)
1106
    {
1107 58
        if ($method == '') {
1108
            $method = $this->method;
1109
        }
1110 58
        if (!$this->no_multicall) {
1111 39
            $results = $this->_try_multicall($reqs, $timeout, $method);
1112 39
            if (is_array($results)) {
1113
                // System.multicall succeeded
1114 39
                return $results;
1115
            } else {
1116
                // either system.multicall is unsupported by server,
1117
                // or call failed for some other reason.
1118
                if ($fallback) {
1119
                    // Don't try it next time...
1120
                    $this->no_multicall = true;
1121
                } else {
1122
                    if (is_a($results, '\PhpXmlRpc\Response')) {
0 ignored issues
show
Bug introduced by
It seems like $results can also be of type false; however, parameter $object of is_a() does only seem to accept object|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

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

1242
            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...
1243
                return false;       // bad return type from system.multicall
1244
            }
1245 20
            $numRets = $rets->count();
1246 20
            if ($numRets != count($reqs)) {
1247
                return false;       // wrong number of return values.
1248
            }
1249
1250 20
            $response = array();
1251 20
            foreach($rets as $val) {
0 ignored issues
show
Bug introduced by
The expression $rets of type integer is not traversable.
Loading history...
1252 20
                switch ($val->kindOf()) {
1253 20
                    case 'array':
1254 20
                        if ($val->count() != 1) {
1255
                            return false;       // Bad value
1256
                        }
1257
                        // Normal return value
1258 20
                        $response[] = new Response($val[0]);
1259 20
                        break;
1260 20
                    case 'struct':
1261 20
                        $code = $val['faultCode'];
1262
                        /** @var Value $code */
1263 20
                        if ($code->kindOf() != 'scalar' || $code->scalartyp() != 'int') {
1264
                            return false;
1265
                        }
1266 20
                        $str = $val['faultString'];
1267
                        /** @var Value $str */
1268 20
                        if ($str->kindOf() != 'scalar' || $str->scalartyp() != 'string') {
1269
                            return false;
1270
                        }
1271 20
                        $response[] = new Response(0, $code->scalarval(), $str->scalarval());
1272 20
                        break;
1273
                    default:
1274
                        return false;
1275
                }
1276
            }
1277
1278 20
            return $response;
1279
        }
1280
    }
1281
}
1282