Passed
Push — master ( 526b0d...b337d2 )
by Gaetano
04:53
created

Client::sendPayloadHTTP10()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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

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

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

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
654
        $sslVersion = 0)
0 ignored issues
show
Unused Code introduced by
The parameter $sslVersion is not used and could be removed. ( Ignorable by Annotation )

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

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

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

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

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

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

1089
            $resp = $req->parseResponse(/** @scrutinizer ignore-type */ $result, true, $this->return_type);
Loading history...
1090
            // if we got back a 302, we can not reuse the curl handle for later calls
1091 242
            if ($resp->faultCode() == PhpXmlRpc::$xmlrpcerr['http_error'] && $keepAlive) {
1092
                curl_close($curl);
1093
                $this->xmlrpc_curl_handle = null;
1094
            }
1095
        }
1096
1097 242
        return $resp;
1098
    }
1099
1100
    /**
1101
     * Send an array of requests and return an array of responses.
1102
     *
1103
     * Unless $this->no_multicall has been set to true, it will try first to use one single xmlrpc call to server method
1104
     * system.multicall, and revert to sending many successive calls in case of failure.
1105
     * This failure is also stored in $this->no_multicall for subsequent calls.
1106
     * Unfortunately, there is no server error code universally used to denote the fact that multicall is unsupported,
1107
     * so there is no way to reliably distinguish between that and a temporary failure.
1108
     * If you are sure that server supports multicall and do not want to fallback to using many single calls, set the
1109
     * fourth parameter to FALSE.
1110
     *
1111
     * NB: trying to shoehorn extra functionality into existing syntax has resulted
1112
     * in pretty much convoluted code...
1113
     *
1114
     * @param Request[] $reqs an array of Request objects
1115
     * @param integer $timeout connection timeout (in seconds). See the details in the docs for the send() method
1116
     * @param string $method the http protocol variant to be used. See the details in the docs for the send() method
1117
     * @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...
1118
     *                         attempted
1119
     *
1120
     * @return Response[]
1121
     */
1122 59
    public function multicall($reqs, $timeout = 0, $method = '', $fallback = true)
1123
    {
1124 59
        if ($method == '') {
1125
            $method = $this->method;
1126
        }
1127 59
        if (!$this->no_multicall) {
1128 40
            $results = $this->_try_multicall($reqs, $timeout, $method);
1129 40
            if (is_array($results)) {
1130
                // System.multicall succeeded
1131 40
                return $results;
1132
            } else {
1133
                // either system.multicall is unsupported by server,
1134
                // or call failed for some other reason.
1135
                if ($fallback) {
1136
                    // Don't try it next time...
1137
                    $this->no_multicall = true;
1138
                } else {
1139
                    if (is_a($results, '\PhpXmlRpc\Response')) {
1140
                        $result = $results;
1141
                    } else {
1142
                        $result = new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'], PhpXmlRpc::$xmlrpcstr['multicall_error']);
1143
                    }
1144
                }
1145
            }
1146
        } else {
1147
            // override fallback, in case careless user tries to do two
1148
            // opposite things at the same time
1149 20
            $fallback = true;
1150
        }
1151
1152 20
        $results = array();
1153 20
        if ($fallback) {
1154
            // system.multicall is (probably) unsupported by server:
1155
            // emulate multicall via multiple requests
1156 20
            foreach ($reqs as $req) {
1157 20
                $results[] = $this->send($req, $timeout, $method);
1158
            }
1159
        } else {
1160
            // user does NOT want to fallback on many single calls:
1161
            // since we should always return an array of responses,
1162
            // return an array with the same error repeated n times
1163
            foreach ($reqs as $req) {
1164
                $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...
1165
            }
1166
        }
1167
1168 20
        return $results;
1169
    }
1170
1171
    /**
1172
     * Attempt to boxcar $reqs via system.multicall.
1173
     *
1174
     * Returns either an array of Response, a single error Response or false (when received response does not respect
1175
     * valid multicall syntax).
1176
     *
1177
     * @param Request[] $reqs
1178
     * @param int $timeout
1179
     * @param string $method
1180
     * @return Response[]|bool|mixed|Response
1181
     */
1182 40
    private function _try_multicall($reqs, $timeout, $method)
1183
    {
1184
        // Construct multicall request
1185 40
        $calls = array();
1186 40
        foreach ($reqs as $req) {
1187 40
            $call['methodName'] = new Value($req->method(), 'string');
1188 40
            $numParams = $req->getNumParams();
1189 40
            $params = array();
1190 40
            for ($i = 0; $i < $numParams; $i++) {
1191 40
                $params[$i] = $req->getParam($i);
1192
            }
1193 40
            $call['params'] = new Value($params, 'array');
1194 40
            $calls[] = new Value($call, 'struct');
1195
        }
1196 40
        $multiCall = new Request('system.multicall');
1197 40
        $multiCall->addParam(new Value($calls, 'array'));
1198
1199
        // Attempt RPC call
1200 40
        $result = $this->send($multiCall, $timeout, $method);
1201
1202 40
        if ($result->faultCode() != 0) {
1203
            // call to system.multicall failed
1204
            return $result;
1205
        }
1206
1207
        // Unpack responses.
1208 40
        $rets = $result->value();
1209
1210 40
        if ($this->return_type == 'xml') {
1211
            return $rets;
1212 40
        } elseif ($this->return_type == 'phpvals') {
1213
            /// @todo test this code branch...
1214 20
            $rets = $result->value();
1215 20
            if (!is_array($rets)) {
0 ignored issues
show
introduced by
The condition is_array($rets) is always false.
Loading history...
1216
                return false;       // bad return type from system.multicall
1217
            }
1218 20
            $numRets = count($rets);
1219 20
            if ($numRets != count($reqs)) {
1220
                return false;       // wrong number of return values.
1221
            }
1222
1223 20
            $response = array();
1224 20
            for ($i = 0; $i < $numRets; $i++) {
1225 20
                $val = $rets[$i];
1226 20
                if (!is_array($val)) {
1227
                    return false;
1228
                }
1229 20
                switch (count($val)) {
1230 20
                    case 1:
1231 20
                        if (!isset($val[0])) {
1232
                            return false;       // Bad value
1233
                        }
1234
                        // Normal return value
1235 20
                        $response[$i] = new Response($val[0], 0, '', 'phpvals');
1236 20
                        break;
1237 20
                    case 2:
1238
                        /// @todo remove usage of @: it is apparently quite slow
1239 20
                        $code = @$val['faultCode'];
1240 20
                        if (!is_int($code)) {
1241
                            return false;
1242
                        }
1243 20
                        $str = @$val['faultString'];
1244 20
                        if (!is_string($str)) {
1245
                            return false;
1246
                        }
1247 20
                        $response[$i] = new Response(0, $code, $str);
1248 20
                        break;
1249
                    default:
1250
                        return false;
1251
                }
1252
            }
1253
1254 20
            return $response;
1255
        } else {
1256
            // return type == 'xmlrpcvals'
1257
1258 21
            $rets = $result->value();
1259 21
            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

1259
            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...
1260
                return false;       // bad return type from system.multicall
1261
            }
1262 21
            $numRets = $rets->count();
1263 21
            if ($numRets != count($reqs)) {
1264
                return false;       // wrong number of return values.
1265
            }
1266
1267 21
            $response = array();
1268 21
            foreach($rets as $val) {
0 ignored issues
show
Bug introduced by
The expression $rets of type integer is not traversable.
Loading history...
1269 21
                switch ($val->kindOf()) {
1270 21
                    case 'array':
1271 21
                        if ($val->count() != 1) {
1272
                            return false;       // Bad value
1273
                        }
1274
                        // Normal return value
1275 21
                        $response[] = new Response($val[0]);
1276 21
                        break;
1277 20
                    case 'struct':
1278 20
                        $code = $val['faultCode'];
1279
                        /** @var Value $code */
1280 20
                        if ($code->kindOf() != 'scalar' || $code->scalartyp() != 'int') {
1281
                            return false;
1282
                        }
1283 20
                        $str = $val['faultString'];
1284
                        /** @var Value $str */
1285 20
                        if ($str->kindOf() != 'scalar' || $str->scalartyp() != 'string') {
1286
                            return false;
1287
                        }
1288 20
                        $response[] = new Response(0, $code->scalarval(), $str->scalarval());
1289 20
                        break;
1290
                    default:
1291
                        return false;
1292
                }
1293
            }
1294
1295 21
            return $response;
1296
        }
1297
    }
1298
}
1299