Passed
Push — master ( 593257...e1cd2d )
by Gaetano
03:24
created

Client::sendPayloadSocket()   F

Complexity

Conditions 42
Paths > 20000

Size

Total Lines 186
Code Lines 121

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 82
CRAP Score 83.7007

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
ccs 82
cts 115
cp 0.713
crap 83.7007
rs 0

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 512
    public function __construct($path, $server = '', $port = '', $method = '')
131
    {
132
        // allow user to specify all params in $path
133 512
        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 512
        if ($path == '' || $path[0] != '/') {
157
            $this->path = '/' . $path;
158
        } else {
159 512
            $this->path = $path;
160
        }
161 512
        $this->server = $server;
162 512
        if ($port != '') {
163 3
            $this->port = $port;
164
        }
165 512
        if ($method != '') {
166
            $this->method = $method;
167
        }
168
169
        // if ZLIB is enabled, let the client by default accept compressed responses
170 512
        if (function_exists('gzinflate') || (
171
                function_exists('curl_init') && (($info = curl_version()) &&
172 512
                    ((is_string($info) && strpos($info, 'zlib') !== null) || isset($info['libz_version'])))
173
            )
174
        ) {
175 512
            $this->accepted_compression = array('gzip', 'deflate');
176
        }
177
178
        // keepalives: enabled by default
179 512
        $this->keepalive = true;
180
181
        // by default the xml parser can support these 3 charset encodings
182 512
        $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 512
        $this->user_agent = PhpXmlRpc::$xmlrpcName . ' ' . PhpXmlRpc::$xmlrpcVersion;
196 512
    }
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 512
    public function setDebug($level)
212
    {
213 512
        $this->debug = $level;
214 512
    }
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 62
    public function setSSLVerifyPeer($i)
295
    {
296 62
        $this->verifypeer = $i;
297 62
    }
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 62
    public function setSSLVerifyHost($i)
307
    {
308 62
        $this->verifyhost = $i;
309 62
    }
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 62
    public function setSSLVersion($i)
317
    {
318 62
        $this->sslversion = $i;
319 62
    }
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
    public function setProxy($proxyHost, $proxyPort, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1)
334
    {
335
        $this->proxy = $proxyHost;
336
        $this->proxyport = $proxyPort;
337
        $this->proxy_user = $proxyUsername;
338
        $this->proxy_pass = $proxyPassword;
339
        $this->proxy_authtype = $proxyAuthType;
340
    }
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 429
    public function setCookie($name, $value = '', $path = '', $domain = '', $port = null)
397
    {
398 429
        $this->cookies[$name]['value'] = rawurlencode($value);
399 429
        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 429
            $this->cookies[$name]['version'] = 0;
406
        }
407 429
    }
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 496
    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 496
        if ($method == '') {
472 67
            $method = $this->method;
473
        }
474
475 496
        if (is_array($req)) {
476
            // $req is an array of Requests
477 49
            $r = $this->multicall($req, $timeout, $method);
478
479 49
            return $r;
480 496
        } elseif (is_string($req)) {
481 23
            $n = new Request('');
482 23
            $n->payload = $req;
483 23
            $req = $n;
484
        }
485
486
        // where req is a Request
487 496
        $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 496
        $useCurl = ($this->use_curl == self::USE_CURL_ALWAYS) || ($this->use_curl == self::USE_CURL_AUTO &&
492 496
            ($method == 'https' || $method == 'http11'));
493
494 496
        if ($useCurl) {
495 182
            $r = $this->sendPayloadCURL(
496 182
                $req,
497 182
                $this->server,
498 182
                $this->port,
499
                $timeout,
500 182
                $this->username,
501 182
                $this->password,
502 182
                $this->authtype,
503 182
                $this->cert,
504 182
                $this->certpass,
505 182
                $this->cacert,
506 182
                $this->cacertdir,
507 182
                $this->proxy,
508 182
                $this->proxyport,
509 182
                $this->proxy_user,
510 182
                $this->proxy_pass,
511 182
                $this->proxy_authtype,
512
                // bc
513 182
                $method == 'http11' ? 'http' : $method,
514 182
                $this->keepalive,
515 182
                $this->key,
516 182
                $this->keypass,
517 182
                $this->sslversion
518
            );
519
        } else {
520
            // plain 'http 1.0': default to using socket
521 314
            $r = $this->sendPayloadSocket(
522 314
                $req,
523 314
                $this->server,
524 314
                $this->port,
525
                $timeout,
526 314
                $this->username,
527 314
                $this->password,
528 314
                $this->authtype,
529 314
                $this->cert,
530 314
                $this->certpass,
531 314
                $this->cacert,
532 314
                $this->cacertdir,
533 314
                $this->proxy,
534 314
                $this->proxyport,
535 314
                $this->proxy_user,
536 314
                $this->proxy_pass,
537 314
                $this->proxy_authtype,
538
                $method,
539 314
                $this->key,
540 314
                $this->keypass,
541 314
                $this->sslversion
542
            );
543
        }
544
545 496
        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 314
    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 314
        if ($port == 0) {
640 312
            $port = ( $method === 'https' ) ? 443 : 80;
641
        }
642
643
        // Only create the payload if it was not created previously
644 314
        if (empty($req->payload)) {
645 288
            $req->createPayload($this->request_charset_encoding);
646
        }
647
648 314
        $payload = $req->payload;
649
        // Deflate request body and set appropriate request headers
650 314
        $encodingHdr = '';
651 314
        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 314
        $credentials = '';
669 314
        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 314
        $acceptedEncoding = '';
677 314
        if (is_array($this->accepted_compression) && count($this->accepted_compression)) {
678 62
            $acceptedEncoding = 'Accept-Encoding: ' . implode(', ', $this->accepted_compression) . "\r\n";
679
        }
680
681 314
        $proxyCredentials = '';
682 314
        if ($proxyHost) {
683
            if ($proxyPort == 0) {
684
                $proxyPort = 8080;
685
            }
686
            $connectServer = $proxyHost;
687
            $connectPort = $proxyPort;
688
            $transport = 'tcp';
689
            $uri = 'http://' . $server . ':' . $port . $this->path;
690
            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
                $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 314
        $cookieHeader = '';
705 314
        if (count($this->cookies)) {
706 272
            $version = '';
707 272
            foreach ($this->cookies as $name => $cookie) {
708 272
                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 272
                    $cookieHeader .= ' ' . $name . '=' . $cookie['value'] . ";";
722
                }
723
            }
724 272
            $cookieHeader = 'Cookie:' . $version . substr($cookieHeader, 0, -1) . "\r\n";
725
        }
726
727
        // omit port if default
728 314
        if (($port == 80 && in_array($method, array('http', 'http10'))) || ($port == 443 && $method == 'https')) {
729 314
            $port =  '';
730
        } else {
731
            $port = ':' . $port;
732
        }
733
734 314
        $op = 'POST ' . $uri . " HTTP/1.0\r\n" .
735 314
            'User-Agent: ' . $this->user_agent . "\r\n" .
736 314
            'Host: ' . $server . $port . "\r\n" .
737 314
            $credentials .
738 314
            $proxyCredentials .
739 314
            $acceptedEncoding .
740 314
            $encodingHdr .
741 314
            'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings) . "\r\n" .
742 314
            $cookieHeader .
743 314
            'Content-Type: ' . $req->content_type . "\r\nContent-Length: " .
744 314
            strlen($payload) . "\r\n\r\n" .
745 314
            $payload;
746
747 314
        if ($this->debug > 1) {
748
            Logger::instance()->debugMessage("---SENDING---\n$op\n---END---");
749
        }
750
751 314
        $contextOptions = array();
752 314
        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 314
        $context = stream_context_create($contextOptions);
772
773 314
        if ($timeout <= 0) {
774 40
            $connectTimeout = ini_get('default_socket_timeout');
775
        } else {
776 274
            $connectTimeout = $timeout;
777
        }
778
779 314
        $this->errno = 0;
780 314
        $this->errstr = '';
781
782 314
        $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 314
            STREAM_CLIENT_CONNECT, $context);
784 314
        if ($fp) {
0 ignored issues
show
introduced by
$fp is of type false|resource, thus it always evaluated to false.
Loading history...
785 313
            if ($timeout > 0) {
786 313
                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 313
        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 313
        $ipd = '';
810
        do {
811
            // shall we check for $data === FALSE?
812
            // as per the manual, it signals an error
813 313
            $ipd .= fread($fp, 32768);
814 313
        } while (!feof($fp));
815 313
        fclose($fp);
816
817 313
        $r = $req->parseResponse($ipd, false, $this->return_type);
818
819 313
        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 182
    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 182
        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 182
        if ($method == 'https') {
860 30
            if (($info = curl_version()) &&
861 30
                ((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 182
        if ($port == 0) {
869 181
            if (in_array($method, array('http', 'http10', 'http11'))) {
870 151
                $port = 80;
871
            } else {
872 30
                $port = 443;
873
            }
874
        }
875
876
        // Only create the payload if it was not created previously
877 182
        if (empty($req->payload)) {
878 170
            $req->createPayload($this->request_charset_encoding);
879
        }
880
881
        // Deflate request body and set appropriate request headers
882 182
        $payload = $req->payload;
883 182
        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 122
            $encodingHdr = '';
899
        }
900
901 182
        if ($this->debug > 1) {
902
            Logger::instance()->debugMessage("---SENDING---\n$payload\n---END---");
903
        }
904
905 182
        if (!$keepAlive || !$this->xmlrpc_curl_handle) {
906 182
            if ($method == 'http11' || $method == 'http10') {
907 30
                $protocol = 'http';
908
            } else {
909 152
                $protocol = $method;
910
            }
911 182
            $curl = curl_init($protocol . '://' . $server . ':' . $port . $this->path);
912 182
            if ($keepAlive) {
913 182
                $this->xmlrpc_curl_handle = $curl;
914
            }
915
        } else {
916 23
            $curl = $this->xmlrpc_curl_handle;
917
        }
918
919
        // results into variable
920 182
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
921
922 182
        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 182
        curl_setopt($curl, CURLOPT_USERAGENT, $this->user_agent);
927
        // required for XMLRPC: post the data
928 182
        curl_setopt($curl, CURLOPT_POST, 1);
929
        // the data
930 182
        curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
931
932
        // return the header too
933 182
        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 182
        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 182
        $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 182
        if (!$keepAlive) {
950 120
            $headers[] = 'Connection: close';
951
        }
952
        // request compression header
953 182
        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 182
        $headers[] = 'Expect:';
960
961 182
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
962
        // timeout is borked
963 182
        if ($timeout) {
964 158
            curl_setopt($curl, CURLOPT_TIMEOUT, $timeout == 1 ? 1 : $timeout - 1);
965
        }
966
967 182
        if ($method == 'http10') {
968 30
            curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
969 152
        } elseif ($method == 'http11') {
970
            curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
971
        }
972
973 182
        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 182
        if ($method == 'https') {
983
            // set cert file
984 30
            if ($cert) {
985
                curl_setopt($curl, CURLOPT_SSLCERT, $cert);
986
            }
987
            // set cert password
988 30
            if ($certPass) {
989
                curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $certPass);
990
            }
991
            // whether to verify remote host's cert
992 30
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verifypeer);
993
            // set ca certificates file/dir
994 30
            if ($caCert) {
995
                curl_setopt($curl, CURLOPT_CAINFO, $caCert);
996
            }
997 30
            if ($caCertDir) {
998
                curl_setopt($curl, CURLOPT_CAPATH, $caCertDir);
999
            }
1000
            // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1001 30
            if ($key) {
1002
                curl_setopt($curl, CURLOPT_SSLKEY, $key);
1003
            }
1004
            // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1005 30
            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 30
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $this->verifyhost);
1011
            // allow usage of different SSL versions
1012 30
            curl_setopt($curl, CURLOPT_SSLVERSION, $sslVersion);
1013
        }
1014
1015
        // proxy info
1016 182
        if ($proxyHost) {
1017
            if ($proxyPort == 0) {
1018
                $proxyPort = 8080; // NB: even for HTTPS, local connection is on port 8080
1019
            }
1020
            curl_setopt($curl, CURLOPT_PROXY, $proxyHost . ':' . $proxyPort);
1021
            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 182
        if (count($this->cookies)) {
1034 157
            $cookieHeader = '';
1035 157
            foreach ($this->cookies as $name => $cookie) {
1036 157
                $cookieHeader .= $name . '=' . $cookie['value'] . '; ';
1037
            }
1038 157
            curl_setopt($curl, CURLOPT_COOKIE, substr($cookieHeader, 0, -2));
1039
        }
1040
1041 182
        foreach ($this->extracurlopts as $opt => $val) {
1042
            curl_setopt($curl, $opt, $val);
1043
        }
1044
1045 182
        $result = curl_exec($curl);
1046
1047 182
        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 182
        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 182
            if (!$keepAlive) {
1070 120
                curl_close($curl);
1071
            }
1072 182
            $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 182
            if ($resp->faultCode() == PhpXmlRpc::$xmlrpcerr['http_error'] && $keepAlive) {
1075
                curl_close($curl);
1076
                $this->xmlrpc_curl_handle = null;
1077
            }
1078
        }
1079
1080 182
        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 49
    public function multicall($reqs, $timeout = 0, $method = '', $fallback = true)
1106
    {
1107 49
        if ($method == '') {
1108
            $method = $this->method;
1109
        }
1110 49
        if (!$this->no_multicall) {
1111 33
            $results = $this->_try_multicall($reqs, $timeout, $method);
1112 33
            if (is_array($results)) {
1113
                // System.multicall succeeded
1114 33
                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 17
            $fallback = true;
1133
        }
1134
1135 17
        $results = array();
1136 17
        if ($fallback) {
1137
            // system.multicall is (probably) unsupported by server:
1138
            // emulate multicall via multiple requests
1139 17
            foreach ($reqs as $req) {
1140 17
                $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 17
        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 33
    private function _try_multicall($reqs, $timeout, $method)
1166
    {
1167
        // Construct multicall request
1168 33
        $calls = array();
1169 33
        foreach ($reqs as $req) {
1170 33
            $call['methodName'] = new Value($req->method(), 'string');
1171 33
            $numParams = $req->getNumParams();
1172 33
            $params = array();
1173 33
            for ($i = 0; $i < $numParams; $i++) {
1174 33
                $params[$i] = $req->getParam($i);
1175
            }
1176 33
            $call['params'] = new Value($params, 'array');
1177 33
            $calls[] = new Value($call, 'struct');
1178
        }
1179 33
        $multiCall = new Request('system.multicall');
1180 33
        $multiCall->addParam(new Value($calls, 'array'));
1181
1182
        // Attempt RPC call
1183 33
        $result = $this->send($multiCall, $timeout, $method);
1184
1185 33
        if ($result->faultCode() != 0) {
1186
            // call to system.multicall failed
1187
            return $result;
1188
        }
1189
1190
        // Unpack responses.
1191 33
        $rets = $result->value();
1192
1193 33
        if ($this->return_type == 'xml') {
1194
            return $rets;
1195 33
        } elseif ($this->return_type == 'phpvals') {
1196
            /// @todo test this code branch...
1197 17
            $rets = $result->value();
1198 17
            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 17
            $numRets = count($rets);
1202 17
            if ($numRets != count($reqs)) {
1203
                return false;       // wrong number of return values.
1204
            }
1205
1206 17
            $response = array();
1207 17
            for ($i = 0; $i < $numRets; $i++) {
1208 17
                $val = $rets[$i];
1209 17
                if (!is_array($val)) {
1210
                    return false;
1211
                }
1212 17
                switch (count($val)) {
1213 17
                    case 1:
1214 17
                        if (!isset($val[0])) {
1215
                            return false;       // Bad value
1216
                        }
1217
                        // Normal return value
1218 17
                        $response[$i] = new Response($val[0], 0, '', 'phpvals');
1219 17
                        break;
1220 17
                    case 2:
1221
                        /// @todo remove usage of @: it is apparently quite slow
1222 17
                        $code = @$val['faultCode'];
1223 17
                        if (!is_int($code)) {
1224
                            return false;
1225
                        }
1226 17
                        $str = @$val['faultString'];
1227 17
                        if (!is_string($str)) {
1228
                            return false;
1229
                        }
1230 17
                        $response[$i] = new Response(0, $code, $str);
1231 17
                        break;
1232
                    default:
1233
                        return false;
1234
                }
1235
            }
1236
1237 17
            return $response;
1238
        } else {
1239
            // return type == 'xmlrpcvals'
1240
1241 17
            $rets = $result->value();
1242 17
            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 17
            $numRets = $rets->count();
1246 17
            if ($numRets != count($reqs)) {
1247
                return false;       // wrong number of return values.
1248
            }
1249
1250 17
            $response = array();
1251 17
            foreach($rets as $val) {
0 ignored issues
show
Bug introduced by
The expression $rets of type integer is not traversable.
Loading history...
1252 17
                switch ($val->kindOf()) {
1253 17
                    case 'array':
1254 17
                        if ($val->count() != 1) {
1255
                            return false;       // Bad value
1256
                        }
1257
                        // Normal return value
1258 17
                        $response[] = new Response($val[0]);
1259 17
                        break;
1260 17
                    case 'struct':
1261 17
                        $code = $val['faultCode'];
1262
                        /** @var Value $code */
1263 17
                        if ($code->kindOf() != 'scalar' || $code->scalartyp() != 'int') {
1264
                            return false;
1265
                        }
1266 17
                        $str = $val['faultString'];
1267
                        /** @var Value $str */
1268 17
                        if ($str->kindOf() != 'scalar' || $str->scalartyp() != 'string') {
1269
                            return false;
1270
                        }
1271 17
                        $response[] = new Response(0, $code->scalarval(), $str->scalarval());
1272 17
                        break;
1273
                    default:
1274
                        return false;
1275
                }
1276
            }
1277
1278 17
            return $response;
1279
        }
1280
    }
1281
}
1282