Passed
Push — master ( 0336f4...58f541 )
by Gaetano
05:32
created

Client::prepareCurlHandle()   F

Complexity

Conditions 50
Paths > 20000

Size

Total Lines 197
Code Lines 110

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 85
CRAP Score 65.2459

Importance

Changes 0
Metric Value
cc 50
eloc 110
c 0
b 0
f 0
nc 943488000
nop 21
dl 0
loc 197
ccs 85
cts 104
cp 0.8173
crap 65.2459
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
use PhpXmlRpc\Helper\XMLParser;
7
8
/**
9
 * Used to represent a client of an XML-RPC server.
10
 */
11
class Client
12
{
13
    const USE_CURL_NEVER = 0;
14
    const USE_CURL_ALWAYS = 1;
15
    const USE_CURL_AUTO = 2;
16
17
    protected static $logger;
18
19
    /// @todo: do these need to be public?
20
    public $method = 'http';
21
    public $server;
22
    public $port = 0;
23
    public $path;
24
25
    public $errno;
26
    public $errstr;
27
    public $debug = 0;
28
29
    public $username = '';
30
    public $password = '';
31
    public $authtype = 1;
32
33
    public $cert = '';
34
    public $certpass = '';
35
    public $cacert = '';
36
    public $cacertdir = '';
37
    public $key = '';
38
    public $keypass = '';
39
    public $verifypeer = true;
40
    public $verifyhost = 2;
41
    public $sslversion = 0; // corresponds to CURL_SSLVERSION_DEFAULT
42
43
    public $proxy = '';
44
    public $proxyport = 0;
45
    public $proxy_user = '';
46
    public $proxy_pass = '';
47
    public $proxy_authtype = 1;
48
49
    public $cookies = array();
50
    public $extracurlopts = array();
51
    public $use_curl = self::USE_CURL_AUTO;
52
53
    /**
54
     * @var bool
55
     *
56
     * This determines whether the multicall() method will try to take advantage of the system.multicall xmlrpc method
57
     * to dispatch to the server an array of requests in a single http roundtrip or simply execute many consecutive http
58
     * calls. Defaults to FALSE, but it will be enabled automatically on the first failure of execution of
59
     * system.multicall.
60
     */
61
    public $no_multicall = false;
62
63
    /**
64
     * List of http compression methods accepted by the client for responses.
65
     * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib.
66
     *
67
     * 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
68
     * decide the compression methods it supports. You might check for the presence of 'zlib' in the output of
69
     * curl_version() to determine whether compression is supported or not
70
     */
71
    public $accepted_compression = array();
72
73
    /**
74
     * Name of compression scheme to be used for sending requests.
75
     * Either null, gzip or deflate.
76
     */
77
    public $request_compression = '';
78
79
    /**
80
     * CURL handle: used for keep-alive connections (PHP 4.3.8 up, see:
81
     * http://curl.haxx.se/docs/faq.html#7.3).
82
     * @internal
83
     */
84
    public $xmlrpc_curl_handle = null;
85
86
    /// Whether to use persistent connections for http 1.1 and https
87
    public $keepalive = false;
88
89
    /// Charset encodings that can be decoded without problems by the client
90
    public $accepted_charset_encodings = array();
91
92
    /**
93
     * The charset encoding that will be used for serializing request sent by the client.
94
     * It defaults to NULL, which means using US-ASCII and encoding all characters outside of the ASCII printable range
95
     * using their xml character entity representation (this has the benefit that line end characters will not be mangled
96
     * in the transfer, a CR-LF will be preserved as well as a singe LF).
97
     * Valid values are 'US-ASCII', 'UTF-8' and 'ISO-8859-1'.
98
     * For the fastest mode of operation, set your both your app internal encoding as well as this to UTF-8.
99
     */
100
    public $request_charset_encoding = '';
101
102
    /**
103
     * Decides the content of Response objects returned by calls to send() and multicall().
104
     * Valid values are 'xmlrpcvals', 'phpvals' or 'xml'.
105
     *
106
     * Determines whether the value returned inside an Response object as results of calls to the send() and multicall()
107
     * methods will be a Value object, a plain php value or a raw xml string.
108
     * Allowed values are 'xmlrpcvals' (the default), 'phpvals' and 'xml'.
109
     * To allow the user to differentiate between a correct and a faulty response, fault responses will be returned as
110
     * Response objects in any case.
111
     * Note that the 'phpvals' setting will yield faster execution times, but some of the information from the original
112
     * response will be lost. It will be e.g. impossible to tell whether a particular php string value was sent by the
113
     * server as an xmlrpc string or base64 value.
114
     */
115
    public $return_type = XMLParser::RETURN_XMLRPCVALS;
116
117
    /**
118
     * Sent to servers in http headers.
119
     */
120
    public $user_agent;
121
122 2
    public function getLogger()
123
    {
124 2
        if (self::$logger === null) {
125 2
            self::$logger = Logger::instance();
126
        }
127 2
        return self::$logger;
128
    }
129
130
    public static function setLogger($logger)
131
    {
132
        self::$logger = $logger;
133
    }
134
135
    /**
136
     * @param string $path either the PATH part of the xmlrpc server URL, or complete server URL (in which case you
137
     *                     should use and empty string for all other parameters)
138
     *                     e.g. /xmlrpc/server.php
139
     *                     e.g. http://phpxmlrpc.sourceforge.net/server.php
140
     *                     e.g. https://james:[email protected]:444/xmlrpcserver?agent=007
141
     *                     e.g. h2://fast-and-secure-services.org/endpoint
142
     * @param string $server the server name / ip address
143
     * @param integer $port the port the server is listening on, when omitted defaults to 80 or 443 depending on
144
     *                      protocol used
145
     * @param string $method the http protocol variant: defaults to 'http'; 'https', 'http11', 'h2' and 'h2c' can
146
     *                       be used if CURL is installed. The value set here can be overridden in any call to $this->send().
147
     *                       Use 'h2' to make the lib attempt to use http/2 over a secure connection, and 'h2c'
148
     *                       for http/2 without tls. Note that 'h2c' will not use the h2c 'upgrade' method, and be
149
     *                       thus incompatible with any server/proxy not supporting http/2. This is because POST
150
     *                       request are not compatible with h2c upgrade.
151
     */
152 717
    public function __construct($path, $server = '', $port = '', $method = '')
153
    {
154
        // allow user to specify all params in $path
155 717
        if ($server == '' && $port == '' && $method == '') {
156 6
            $parts = parse_url($path);
157 6
            $server = $parts['host'];
158 6
            $path = isset($parts['path']) ? $parts['path'] : '';
159 6
            if (isset($parts['query'])) {
160
                $path .= '?' . $parts['query'];
161
            }
162 6
            if (isset($parts['fragment'])) {
163
                $path .= '#' . $parts['fragment'];
164
            }
165 6
            if (isset($parts['port'])) {
166
                $port = $parts['port'];
167
            }
168 6
            if (isset($parts['scheme'])) {
169 6
                $method = $parts['scheme'];
170
            }
171 6
            if (isset($parts['user'])) {
172
                $this->username = $parts['user'];
173
            }
174 6
            if (isset($parts['pass'])) {
175
                $this->password = $parts['pass'];
176
            }
177
        }
178 717
        if ($path == '' || $path[0] != '/') {
179
            $this->path = '/' . $path;
180
        } else {
181 717
            $this->path = $path;
182
        }
183 717
        $this->server = $server;
184 717
        if ($port != '') {
185 3
            $this->port = $port;
186
        }
187 717
        if ($method != '') {
188 6
            $this->method = $method;
189
        }
190
191
        // if ZLIB is enabled, let the client by default accept compressed responses
192 717
        if (function_exists('gzinflate') || (
193
                function_exists('curl_version') && (($info = curl_version()) &&
194 711
                    ((is_string($info) && strpos($info, 'zlib') !== null) || isset($info['libz_version'])))
195
            )
196
        ) {
197 717
            $this->accepted_compression = array('gzip', 'deflate');
198
        }
199
200
        // keepalives: enabled by default
201 717
        $this->keepalive = true;
202
203
        // by default the xml parser can support these 3 charset encodings
204 717
        $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII');
205
206
        // Add all charsets which mbstring can handle, but remove junk not found in IANA registry at
207
        // http://www.iana.org/assignments/character-sets/character-sets.xhtml
208
        // NB: this is disabled to avoid making all the requests sent huge... mbstring supports more than 80 charsets!
209
        /*if (function_exists('mb_list_encodings')) {
210
211
            $encodings = array_diff(mb_list_encodings(), array('pass', 'auto', 'wchar', 'BASE64', 'UUENCODE', 'ASCII',
212
                'HTML-ENTITIES', 'Quoted-Printable', '7bit','8bit', 'byte2be', 'byte2le', 'byte4be', 'byte4le'));
213
            $this->accepted_charset_encodings = array_unique(array_merge($this->accepted_charset_encodings, $encodings));
214
        }*/
215
216
        // initialize user_agent string
217 717
        $this->user_agent = PhpXmlRpc::$xmlrpcName . ' ' . PhpXmlRpc::$xmlrpcVersion;
218 717
    }
219
220
    /**
221
     * Enable/disable the echoing to screen of the xmlrpc responses received. The default is not no output anything.
222
     *
223
     * The debugging information at level 1 includes the raw data returned from the XML-RPC server it was querying
224
     * (including bot HTTP headers and the full XML payload), and the PHP value the client attempts to create to
225
     * represent the value returned by the server
226
     * At level2, the complete payload of the xmlrpc request is also printed, before being sent t the server.
227
     *
228
     * This option can be very useful when debugging servers as it allows you to see exactly what the client sends and
229
     * the server returns.
230
     *
231
     * @param integer $level values 0, 1 and 2 are supported (2 = echo sent msg too, before received response)
232
     */
233 714
    public function setDebug($level)
234
    {
235 714
        $this->debug = $level;
236 714
    }
237
238
    /**
239
     * Sets the username and password for authorizing the client to the server.
240
     *
241
     * With the default (HTTP) transport, this information is used for HTTP Basic authorization.
242
     * Note that username and password can also be set using the class constructor.
243
     * With HTTP 1.1 and HTTPS transport, NTLM and Digest authentication protocols are also supported. To enable them use
244
     * the constants CURLAUTH_DIGEST and CURLAUTH_NTLM as values for the auth type parameter.
245
     *
246
     * @param string $user username
247
     * @param string $password password
248
     * @param integer $authType auth type. See curl_setopt man page for supported auth types. Defaults to CURLAUTH_BASIC
249
     *                          (basic auth). Note that auth types NTLM and Digest will only work if the Curl php
250
     *                          extension is enabled.
251
     */
252 66
    public function setCredentials($user, $password, $authType = 1)
253
    {
254 66
        $this->username = $user;
255 66
        $this->password = $password;
256 66
        $this->authtype = $authType;
257 66
    }
258
259
    /**
260
     * Set the optional certificate and passphrase used in SSL-enabled communication with a remote server.
261
     *
262
     * Note: to retrieve information about the client certificate on the server side, you will need to look into the
263
     * environment variables which are set up by the webserver. Different webservers will typically set up different
264
     * variables.
265
     *
266
     * @param string $cert the name of a file containing a PEM formatted certificate
267
     * @param string $certPass the password required to use it
268
     */
269
    public function setCertificate($cert, $certPass = '')
270
    {
271
        $this->cert = $cert;
272
        $this->certpass = $certPass;
273
    }
274
275
    /**
276
     * Add a CA certificate to verify server with in SSL-enabled communication when SetSSLVerifypeer has been set to TRUE.
277
     *
278
     * See the php manual page about CURLOPT_CAINFO for more details.
279
     *
280
     * @param string $caCert certificate file name (or dir holding certificates)
281
     * @param bool $isDir set to true to indicate cacert is a dir. defaults to false
282
     */
283
    public function setCaCertificate($caCert, $isDir = false)
284
    {
285
        if ($isDir) {
286
            $this->cacertdir = $caCert;
287
        } else {
288
            $this->cacert = $caCert;
289
        }
290
    }
291
292
    /**
293
     * Set attributes for SSL communication: private SSL key.
294
     *
295
     * NB: does not work in older php/curl installs.
296
     * Thanks to Daniel Convissor.
297
     *
298
     * @param string $key The name of a file containing a private SSL key
299
     * @param string $keyPass The secret password needed to use the private SSL key
300
     */
301
    public function setKey($key, $keyPass)
302
    {
303
        $this->key = $key;
304
        $this->keypass = $keyPass;
305
    }
306
307
    /**
308
     * Set attributes for SSL communication: verify the remote host's SSL certificate, and cause the connection to fail
309
     * if the cert verification fails.
310
     *
311
     * By default, verification is enabled.
312
     * To specify custom SSL certificates to validate the server with, use the setCaCertificate method.
313
     *
314
     * @param bool $i enable/disable verification of peer certificate
315
     */
316 132
    public function setSSLVerifyPeer($i)
317
    {
318 132
        $this->verifypeer = $i;
319 132
    }
320
321
    /**
322
     * Set attributes for SSL communication: verify the remote host's SSL certificate's common name (CN).
323
     *
324
     * Note that support for value 1 has been removed in cURL 7.28.1
325
     *
326
     * @param int $i Set to 1 to only the existence of a CN, not that it matches
327
     */
328 132
    public function setSSLVerifyHost($i)
329
    {
330 132
        $this->verifyhost = $i;
331 132
    }
332
333
    /**
334
     * Set attributes for SSL communication: SSL version to use. Best left at 0 (default value): let cURL decide
335
     *
336
     * @param int $i
337
     */
338 132
    public function setSSLVersion($i)
339
    {
340 132
        $this->sslversion = $i;
341 132
    }
342
343
    /**
344
     * Set proxy info.
345
     *
346
     * NB: CURL versions before 7.11.10 cannot use a proxy to communicate with https servers.
347
     *
348
     * @param string $proxyHost
349
     * @param string $proxyPort Defaults to 8080 for HTTP and 443 for HTTPS
350
     * @param string $proxyUsername Leave blank if proxy has public access
351
     * @param string $proxyPassword Leave blank if proxy has public access
352
     * @param int $proxyAuthType defaults to CURLAUTH_BASIC (Basic authentication protocol); set to constant CURLAUTH_NTLM
353
     *                           to use NTLM auth with proxy (has effect only when the client uses the HTTP 1.1 protocol)
354
     */
355 99
    public function setProxy($proxyHost, $proxyPort, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1)
356
    {
357 99
        $this->proxy = $proxyHost;
358 99
        $this->proxyport = $proxyPort;
359 99
        $this->proxy_user = $proxyUsername;
360 99
        $this->proxy_pass = $proxyPassword;
361 99
        $this->proxy_authtype = $proxyAuthType;
362 99
    }
363
364
    /**
365
     * Enables/disables reception of compressed xmlrpc responses.
366
     *
367
     * This requires the "zlib" extension to be enabled in your php install. If it is, by default xmlrpc_client
368
     * instances will enable reception of compressed content.
369
     * Note that enabling reception of compressed responses merely adds some standard http headers to xmlrpc requests.
370
     * It is up to the xmlrpc server to return compressed responses when receiving such requests.
371
     *
372
     * @param string $compMethod either 'gzip', 'deflate', 'any' or ''
373
     */
374
    public function setAcceptedCompression($compMethod)
375
    {
376
        if ($compMethod == 'any') {
377
            $this->accepted_compression = array('gzip', 'deflate');
378
        } 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...
379
            $this->accepted_compression = array();
380
        } else {
381
            $this->accepted_compression = array($compMethod);
382
        }
383
    }
384
385
    /**
386
     * Enables/disables http compression of xmlrpc request.
387
     *
388
     * This requires the "zlib" extension to be enabled in your php install.
389
     * Take care when sending compressed requests: servers might not support them (and automatic fallback to
390
     * uncompressed requests is not yet implemented).
391
     *
392
     * @param string $compMethod either 'gzip', 'deflate' or ''
393
     */
394
    public function setRequestCompression($compMethod)
395
    {
396
        $this->request_compression = $compMethod;
397
    }
398
399
    /**
400
     * Adds a cookie to list of cookies that will be sent to server with every further request (useful e.g. for keeping
401
     * session info outside of the xml-rpc payload).
402
     *
403
     * NB: By default cookies are sent using the 'original/netscape' format, which is also the same as the RFC 2965;
404
     * setting any param but name and value will turn the cookie into a 'version 1' cookie (i.e. RFC 2109 cookie) that
405
     * might not be fully supported by the server. Note that RFC 2109 has currently 'historic' status...
406
     *
407
     * @param string $name nb: will not be escaped in the request's http headers. Take care not to use CTL chars or
408
     *                     separators!
409
     * @param string $value
410
     * @param string $path leave this empty unless the xml-rpc server only accepts RFC 2109 cookies
411
     * @param string $domain leave this empty unless the xml-rpc server only accepts RFC 2109 cookies
412
     * @param int $port leave this empty unless the xml-rpc server only accepts RFC 2109 cookies
413
     *
414
     * @todo check correctness of urlencoding cookie value (copied from php way of doing it, but php is generally sending
415
     *       response not requests. We do the opposite...)
416
     * @todo strip invalid chars from cookie name? As per RFC6265, we should follow RFC2616, Section 2.2
417
     */
418 580
    public function setCookie($name, $value = '', $path = '', $domain = '', $port = null)
419
    {
420 580
        $this->cookies[$name]['value'] = rawurlencode($value);
421 580
        if ($path || $domain || $port) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $port of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
422
            $this->cookies[$name]['path'] = $path;
423
            $this->cookies[$name]['domain'] = $domain;
424
            $this->cookies[$name]['port'] = $port;
425
            $this->cookies[$name]['version'] = 1;
426
        } else {
427 580
            $this->cookies[$name]['version'] = 0;
428
        }
429 580
    }
430
431
    /**
432
     * Directly set cURL options, for extra flexibility (when in cURL mode).
433
     *
434
     * It allows eg. to bind client to a specific IP interface / address.
435
     *
436
     * @param array $options
437
     */
438
    public function setCurlOptions($options)
439
    {
440
        $this->extracurlopts = $options;
441
    }
442
443
    /**
444
     * @param int $useCurlMode self::USE_CURL_ALWAYS, self::USE_CURL_AUTO or self::USE_CURL_NEVER
445
     */
446 66
    public function setUseCurl($useCurlMode)
447
    {
448 66
        $this->use_curl = $useCurlMode;
449 66
    }
450
451
452
    /**
453
     * Set user-agent string that will be used by this client instance in http headers sent to the server.
454
     *
455
     * The default user agent string includes the name of this library and the version number.
456
     *
457
     * @param string $agentString
458
     */
459
    public function setUserAgent($agentString)
460
    {
461
        $this->user_agent = $agentString;
462
    }
463
464
    /**
465
     * Send an xmlrpc request to the server.
466
     *
467
     * @param Request|Request[]|string $req The Request object, or an array of requests for using multicall, or the
468
     *                                      complete xml representation of a request.
469
     *                                      When sending an array of Request objects, the client will try to make use of
470
     *                                      a single 'system.multicall' xml-rpc method call to forward to the server all
471
     *                                      the requests in a single HTTP round trip, unless $this->no_multicall has
472
     *                                      been previously set to TRUE (see the multicall method below), in which case
473
     *                                      many consecutive xmlrpc requests will be sent. The method will return an
474
     *                                      array of Response objects in both cases.
475
     *                                      The third variant allows to build by hand (or any other means) a complete
476
     *                                      xmlrpc request message, and send it to the server. $req should be a string
477
     *                                      containing the complete xml representation of the request. It is e.g. useful
478
     *                                      when, for maximal speed of execution, the request is serialized into a
479
     *                                      string using the native php xmlrpc functions (see http://www.php.net/xmlrpc)
480
     * @param integer $timeout Connection timeout, in seconds, If unspecified, a platform specific timeout will apply.
481
     *                         This timeout value is passed to fsockopen(). It is also used for detecting server
482
     *                         timeouts during communication (i.e. if the server does not send anything to the client
483
     *                         for $timeout seconds, the connection will be closed).
484
     * @param string $method valid values are 'http', 'http11', 'https', 'h2' and 'h2c'. If left unspecified,
485
     *                       the http protocol chosen during creation of the object will be used.
486
     *                       Use 'h2' to make the lib attempt to use http/2 over a secure connection, and 'h2c'
487
     *                       for http/2 without tls. Note that 'h2c' will not use the h2c 'upgrade' method, and be
488
     *                       thus incompatible with any server/proxy not supporting http/2. This is because POST
489
     *                       request are not compatible with h2c upgrade.
490
     *
491
     * @return Response|Response[] Note that the client will always return a Response object, even if the call fails
492
     * @todo allow throwing exceptions instead of returning responses in case of failed calls and/or Fault responses
493
     * @todo refactor: we now support many options besides connection timeout and http version to use. Why only privilege those?
494
     */
495 696
    public function send($req, $timeout = 0, $method = '')
496
    {
497
        // if user does not specify http protocol, use native method of this client
498
        // (i.e. method set during call to constructor)
499 696
        if ($method == '') {
500 116
            $method = $this->method;
501
        }
502
503 696
        if (is_array($req)) {
504
            // $req is an array of Requests
505 65
            $r = $this->multicall($req, $timeout, $method);
506
507 65
            return $r;
508 696
        } elseif (is_string($req)) {
509 28
            $n = new Request('');
510 28
            $n->payload = $req;
511 28
            $req = $n;
512
        }
513
514
        // where req is a Request
515 696
        $req->setDebug($this->debug);
516
517
        /// @todo we could be smarter about this and force usage of curl in scenarios where it is both available and
518
        ///       needed, such as digest or ntlm auth. Do not attempt to use it for https if not present
519 696
        $useCurl = ($this->use_curl == self::USE_CURL_ALWAYS) || ($this->use_curl == self::USE_CURL_AUTO &&
520 696
            (in_array($method, array('https', 'http11', 'h2c', 'h2'))));
521
522 696
        if ($useCurl) {
523 322
            $r = $this->sendPayloadCURL(
524 322
                $req,
525 322
                $this->server,
526 322
                $this->port,
527
                $timeout,
528 322
                $this->username,
529 322
                $this->password,
530 322
                $this->authtype,
531 322
                $this->cert,
532 322
                $this->certpass,
533 322
                $this->cacert,
534 322
                $this->cacertdir,
535 322
                $this->proxy,
536 322
                $this->proxyport,
537 322
                $this->proxy_user,
538 322
                $this->proxy_pass,
539 322
                $this->proxy_authtype,
540
                // bc
541 322
                $method == 'http11' ? 'http' : $method,
542 322
                $this->keepalive,
543 322
                $this->key,
544 322
                $this->keypass,
545 322
                $this->sslversion
546
            );
547
        } else {
548
            // plain 'http 1.0': default to using socket
549 374
            $r = $this->sendPayloadSocket(
550 374
                $req,
551 374
                $this->server,
552 374
                $this->port,
553
                $timeout,
554 374
                $this->username,
555 374
                $this->password,
556 374
                $this->authtype,
557 374
                $this->cert,
558 374
                $this->certpass,
559 374
                $this->cacert,
560 374
                $this->cacertdir,
561 374
                $this->proxy,
562 374
                $this->proxyport,
563 374
                $this->proxy_user,
564 374
                $this->proxy_pass,
565 374
                $this->proxy_authtype,
566
                $method,
567 374
                $this->key,
568 374
                $this->keypass,
569 374
                $this->sslversion
570
            );
571
        }
572
573 696
        return $r;
574
    }
575
576
    /**
577
     * @deprecated
578
     * @param Request $req
579
     * @param string $server
580
     * @param int $port
581
     * @param int $timeout
582
     * @param string $username
583
     * @param string $password
584
     * @param int $authType
585
     * @param string $proxyHost
586
     * @param int $proxyPort
587
     * @param string $proxyUsername
588
     * @param string $proxyPassword
589
     * @param int $proxyAuthType
590
     * @param string $method
591
     * @return Response
592
     */
593
    protected function sendPayloadHTTP10($req, $server, $port, $timeout = 0, $username = '', $password = '',
594
        $authType = 1, $proxyHost = '', $proxyPort = 0, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1,
595
        $method='http')
596
    {
597
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
598
599
        return $this->sendPayloadSocket($req, $server, $port, $timeout, $username, $password, $authType, null, null,
600
            null, null, $proxyHost, $proxyPort, $proxyUsername, $proxyPassword, $proxyAuthType, $method);
601
    }
602
603
    /**
604
     * @deprecated
605
     * @param Request $req
606
     * @param string $server
607
     * @param int $port
608
     * @param int $timeout
609
     * @param string $username
610
     * @param string $password
611
     * @param int $authType
612
     * @param string $cert
613
     * @param string $certPass
614
     * @param string $caCert
615
     * @param string $caCertDir
616
     * @param string $proxyHost
617
     * @param int $proxyPort
618
     * @param string $proxyUsername
619
     * @param string $proxyPassword
620
     * @param int $proxyAuthType
621
     * @param bool $keepAlive
622
     * @param string $key
623
     * @param string $keyPass
624
     * @param int $sslVersion
625
     * @return Response
626
     */
627
    protected function sendPayloadHTTPS($req, $server, $port, $timeout = 0, $username = '',  $password = '',
628
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
629
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $keepAlive = false, $key = '', $keyPass = '',
630
        $sslVersion = 0)
631
    {
632
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
633
634
        return $this->sendPayloadCURL($req, $server, $port, $timeout, $username,
635
            $password, $authType, $cert, $certPass, $caCert, $caCertDir, $proxyHost, $proxyPort,
636
            $proxyUsername, $proxyPassword, $proxyAuthType, 'https', $keepAlive, $key, $keyPass, $sslVersion);
637
    }
638
639
    /**
640
     * @param Request $req
641
     * @param string $server
642
     * @param int $port
643
     * @param int $timeout
644
     * @param string $username
645
     * @param string $password
646
     * @param int $authType only value supported is 1
647
     * @param string $cert
648
     * @param string $certPass
649
     * @param string $caCert
650
     * @param string $caCertDir
651
     * @param string $proxyHost
652
     * @param int $proxyPort
653
     * @param string $proxyUsername
654
     * @param string $proxyPassword
655
     * @param int $proxyAuthType only value supported is 1
656
     * @param string $method 'http' (synonym for 'http10'), 'http10' or 'https'
657
     * @param string $key
658
     * @param string $keyPass @todo not implemented yet.
659
     * @param int $sslVersion @todo not implemented yet. See http://php.net/manual/en/migration56.openssl.php
660
     * @return Response
661
     *
662
     * @todo refactor: we get many options for the call passed in, but some we use from $this. We should clean that up
663
     */
664 374
    protected function sendPayloadSocket($req, $server, $port, $timeout = 0, $username = '', $password = '',
665
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
666
        $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

666
        $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...
667
        $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

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

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

815
        $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr, /** @scrutinizer ignore-type */ $connectTimeout,
Loading history...
816 374
            STREAM_CLIENT_CONNECT, $context);
817 374
        if ($fp) {
0 ignored issues
show
introduced by
$fp is of type false|resource, thus it always evaluated to false.
Loading history...
818 373
            if ($timeout > 0) {
819 367
                stream_set_timeout($fp, $timeout);
820
            }
821
        } else {
822 1
            if ($this->errstr == '') {
823
                $err = error_get_last();
824
                $this->errstr = $err['message'];
825
            }
826
827 1
            $this->errstr = 'Connect error: ' . $this->errstr;
828 1
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr . ' (' . $this->errno . ')');
829
830 1
            return $r;
831
        }
832
833 373
        if (!fputs($fp, $op, strlen($op))) {
834
            fclose($fp);
835
            $this->errstr = 'Write error';
836
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr);
837
838
            return $r;
839
        }
840
841
        // Close socket before parsing.
842
        // It should yield slightly better execution times, and make easier recursive calls (e.g. to follow http redirects)
843 373
        $ipd = '';
844
        do {
845
            // shall we check for $data === FALSE?
846
            // as per the manual, it signals an error
847 373
            $ipd .= fread($fp, 32768);
848 373
        } while (!feof($fp));
849 373
        fclose($fp);
850
851 373
        $r = $req->parseResponse($ipd, false, $this->return_type);
852
853 373
        return $r;
854
    }
855
856
    /**
857
     * Contributed by Justin Miller <[email protected]>
858
     * Requires curl to be built into PHP
859
     * NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers!
860
     *
861
     * @param Request $req
862
     * @param string $server
863
     * @param int $port
864
     * @param int $timeout
865
     * @param string $username
866
     * @param string $password
867
     * @param int $authType
868
     * @param string $cert
869
     * @param string $certPass
870
     * @param string $caCert
871
     * @param string $caCertDir
872
     * @param string $proxyHost
873
     * @param int $proxyPort
874
     * @param string $proxyUsername
875
     * @param string $proxyPassword
876
     * @param int $proxyAuthType
877
     * @param string $method 'http' (let curl decide), 'http10', 'http11', 'https', 'h2c' or 'h2'
878
     * @param bool $keepAlive
879
     * @param string $key
880
     * @param string $keyPass
881
     * @param int $sslVersion
882
     * @return Response
883
     *
884
     * @todo refactor: we get many options for the call passed in, but some we use from $this. We should clean that up
885
     */
886 322
    protected function sendPayloadCURL($req, $server, $port, $timeout = 0, $username = '', $password = '',
887
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
888
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'https', $keepAlive = false, $key = '',
889
        $keyPass = '', $sslVersion = 0)
890
    {
891 322
        if (!function_exists('curl_init')) {
892
            $this->errstr = 'CURL unavailable on this install';
893
            return new Response(0, PhpXmlRpc::$xmlrpcerr['no_curl'], PhpXmlRpc::$xmlrpcstr['no_curl']);
894
        }
895 322
        if ($method == 'https' || $method == 'h2') {
896
            // q: what about installs where we get back a string, but curl is linked to other ssl libs than openssl?
897 96
            if (($info = curl_version()) &&
898 96
                ((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...
899
            ) {
900
                $this->errstr = 'SSL unavailable on this install';
901
                return new Response(0, PhpXmlRpc::$xmlrpcerr['no_ssl'], PhpXmlRpc::$xmlrpcstr['no_ssl']);
902
            }
903
        }
904 322
        if (($method == 'h2' && !defined('CURL_HTTP_VERSION_2_0')) ||
905 322
            ($method == 'h2c' && !defined('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE'))) {
906
            $this->errstr = 'HTTP/2 unavailable on this install';
907
            return new Response(0, PhpXmlRpc::$xmlrpcerr['no_http2'], PhpXmlRpc::$xmlrpcstr['no_http2']);
908
        }
909
910 322
        $curl = $this->prepareCurlHandle($req, $server, $port, $timeout, $username, $password,
911
            $authType, $cert, $certPass, $caCert, $caCertDir, $proxyHost, $proxyPort,
912
            $proxyUsername, $proxyPassword, $proxyAuthType, $method, $keepAlive, $key,
913
            $keyPass, $sslVersion);
914
915 322
        $result = curl_exec($curl);
916
917 322
        if ($this->debug > 1) {
918
            $message = "---CURL INFO---\n";
919
            foreach (curl_getinfo($curl) as $name => $val) {
920
                if (is_array($val)) {
921
                    $val = implode("\n", $val);
922
                }
923
                $message .= $name . ': ' . $val . "\n";
924
            }
925
            $message .= '---END---';
926
            $this->getLogger()->debugMessage($message);
927
        }
928
929 322
        if (!$result) {
930
            /// @todo we should use a better check here - what if we get back '' or '0'?
931
932 1
            $this->errstr = 'no response';
933 1
            $resp = new Response(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] . ': ' . curl_error($curl));
934 1
            curl_close($curl);
935 1
            if ($keepAlive) {
936 1
                $this->xmlrpc_curl_handle = null;
937
            }
938
        } else {
939 322
            if (!$keepAlive) {
940 160
                curl_close($curl);
941
            }
942 322
            $resp = $req->parseResponse($result, true, $this->return_type);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type true; however, parameter $data of PhpXmlRpc\Request::parseResponse() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

942
            $resp = $req->parseResponse(/** @scrutinizer ignore-type */ $result, true, $this->return_type);
Loading history...
943
            // if we got back a 302, we can not reuse the curl handle for later calls
944 322
            if ($resp->faultCode() == PhpXmlRpc::$xmlrpcerr['http_error'] && $keepAlive) {
945
                curl_close($curl);
946
                $this->xmlrpc_curl_handle = null;
947
            }
948
        }
949
950 322
        return $resp;
951
    }
952
953 322
    protected function prepareCurlHandle($req, $server, $port, $timeout = 0, $username = '', $password = '',
954
         $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
955
         $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'https', $keepAlive = false, $key = '',
956
         $keyPass = '', $sslVersion = 0)
957
    {
958 322
        if ($port == 0) {
959 321
            if (in_array($method, array('http', 'http10', 'http11', 'h2c'))) {
960 225
                $port = 80;
961
            } else {
962 96
                $port = 443;
963
            }
964
        }
965
966
        // Only create the payload if it was not created previously
967 322
        if (empty($req->payload)) {
968 302
            $req->serialize($this->request_charset_encoding);
969
        }
970
971
        // Deflate request body and set appropriate request headers
972 322
        $payload = $req->payload;
973 322
        if (function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate')) {
974 64
            if ($this->request_compression == 'gzip') {
975 32
                $a = @gzencode($payload);
976 32
                if ($a) {
977 32
                    $payload = $a;
978 32
                    $encodingHdr = 'Content-Encoding: gzip';
979
                }
980
            } else {
981 32
                $a = @gzcompress($payload);
982 32
                if ($a) {
983 32
                    $payload = $a;
984 64
                    $encodingHdr = 'Content-Encoding: deflate';
985
                }
986
            }
987
        } else {
988 258
            $encodingHdr = '';
989
        }
990
991 322
        if (!$keepAlive || !$this->xmlrpc_curl_handle) {
992 322
            if ($method == 'http11' || $method == 'http10' || $method == 'h2c') {
993 64
                $protocol = 'http';
994
            } else {
995 258
                if ($method == 'h2') {
996 32
                    $protocol = 'https';
997
                } else {
998
                    // http, https
999 226
                    $protocol = $method;
1000
                }
1001
            }
1002 322
            $curl = curl_init($protocol . '://' . $server . ':' . $port . $this->path);
1003 322
            if ($keepAlive) {
1004 322
                $this->xmlrpc_curl_handle = $curl;
1005
            }
1006
        } else {
1007 56
            $curl = $this->xmlrpc_curl_handle;
1008
        }
1009
1010
        // results into variable
1011 322
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
1012
1013 322
        if ($this->debug > 1) {
1014
            curl_setopt($curl, CURLOPT_VERBOSE, true);
1015
            /// @todo allow callers to redirect curlopt_stderr to some stream which can be buffered
1016
        }
1017 322
        curl_setopt($curl, CURLOPT_USERAGENT, $this->user_agent);
1018
        // required for XMLRPC: post the data
1019 322
        curl_setopt($curl, CURLOPT_POST, 1);
1020
        // the data
1021 322
        curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
1022
1023
        // return the header too
1024 322
        curl_setopt($curl, CURLOPT_HEADER, 1);
1025
1026
        // NB: if we set an empty string, CURL will add http header indicating
1027
        // ALL methods it is supporting. This is possibly a better option than letting the user tell what curl can / cannot do...
1028 322
        if (is_array($this->accepted_compression) && count($this->accepted_compression)) {
1029
            //curl_setopt($curl, CURLOPT_ENCODING, implode(',', $this->accepted_compression));
1030
            // empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1031 65
            if (count($this->accepted_compression) == 1) {
1032 64
                curl_setopt($curl, CURLOPT_ENCODING, $this->accepted_compression[0]);
1033
            } else {
1034 1
                curl_setopt($curl, CURLOPT_ENCODING, '');
1035
            }
1036
        }
1037
        // extra headers
1038 322
        $headers = array('Content-Type: ' . $req->content_type, 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings));
1039
        // if no keepalive is wanted, let the server know it in advance
1040 322
        if (!$keepAlive) {
1041 160
            $headers[] = 'Connection: close';
1042
        }
1043
        // request compression header
1044 322
        if ($encodingHdr) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $encodingHdr does not seem to be defined for all execution paths leading up to this point.
Loading history...
1045 64
            $headers[] = $encodingHdr;
1046
        }
1047
1048
        // Fix the HTTP/1.1 417 Expectation Failed Bug (curl by default adds a 'Expect: 100-continue' header when POST
1049
        // size exceeds 1025 bytes, apparently)
1050 322
        $headers[] = 'Expect:';
1051
1052 322
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
1053
        // timeout is borked
1054 322
        if ($timeout) {
1055 272
            curl_setopt($curl, CURLOPT_TIMEOUT, $timeout == 1 ? 1 : $timeout - 1);
1056
        }
1057
1058 322
        switch($method) {
1059 322
            case 'http10':
1060 32
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
1061 32
                break;
1062 290
            case 'http11':
1063
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
1064
                break;
1065 290
            case 'h2c':
1066 32
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE);
1067 32
                break;
1068 258
            case 'h2':
1069 32
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
1070 32
                break;
1071
        }
1072
1073 322
        if ($username && $password) {
1074 32
            curl_setopt($curl, CURLOPT_USERPWD, $username . ':' . $password);
1075 32
            if (defined('CURLOPT_HTTPAUTH')) {
1076 32
                curl_setopt($curl, CURLOPT_HTTPAUTH, $authType);
1077
            } elseif ($authType != 1) {
1078
                $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported by the current PHP/curl install');
1079
            }
1080
        }
1081
1082 322
        if ($method == 'https' || $method == 'h2') {
1083
            // set cert file
1084 96
            if ($cert) {
1085
                curl_setopt($curl, CURLOPT_SSLCERT, $cert);
1086
            }
1087
            // set cert password
1088 96
            if ($certPass) {
1089
                curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $certPass);
1090
            }
1091
            // whether to verify remote host's cert
1092 96
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verifypeer);
1093
            // set ca certificates file/dir
1094 96
            if ($caCert) {
1095
                curl_setopt($curl, CURLOPT_CAINFO, $caCert);
1096
            }
1097 96
            if ($caCertDir) {
1098
                curl_setopt($curl, CURLOPT_CAPATH, $caCertDir);
1099
            }
1100
            // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1101 96
            if ($key) {
1102
                curl_setopt($curl, CURLOPT_SSLKEY, $key);
1103
            }
1104
            // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1105 96
            if ($keyPass) {
1106
                curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $keyPass);
1107
            }
1108
            // whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that
1109
            // it matches the hostname used
1110 96
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $this->verifyhost);
1111
            // allow usage of different SSL versions
1112 96
            curl_setopt($curl, CURLOPT_SSLVERSION, $sslVersion);
1113
        }
1114
1115
        // proxy info
1116 322
        if ($proxyHost) {
1117 64
            if ($proxyPort == 0) {
1118
                $proxyPort = 8080; // NB: even for HTTPS, local connection is on port 8080
1119
            }
1120 64
            curl_setopt($curl, CURLOPT_PROXY, $proxyHost . ':' . $proxyPort);
1121 64
            if ($proxyUsername) {
1122
                curl_setopt($curl, CURLOPT_PROXYUSERPWD, $proxyUsername . ':' . $proxyPassword);
1123
                if (defined('CURLOPT_PROXYAUTH')) {
1124
                    curl_setopt($curl, CURLOPT_PROXYAUTH, $proxyAuthType);
1125
                } elseif ($proxyAuthType != 1) {
1126
                    $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported by the current PHP/curl install');
1127
                }
1128
            }
1129
        }
1130
1131
        // NB: should we build cookie http headers by hand rather than let CURL do it?
1132
        // the following code does not honour 'expires', 'path' and 'domain' cookie attributes set to client obj the the user...
1133 322
        if (count($this->cookies)) {
1134 271
            $cookieHeader = '';
1135 271
            foreach ($this->cookies as $name => $cookie) {
1136 271
                $cookieHeader .= $name . '=' . $cookie['value'] . '; ';
1137
            }
1138 271
            curl_setopt($curl, CURLOPT_COOKIE, substr($cookieHeader, 0, -2));
1139
        }
1140
1141 322
        foreach ($this->extracurlopts as $opt => $val) {
1142
            curl_setopt($curl, $opt, $val);
1143
        }
1144
1145 322
        if ($this->debug > 1) {
1146
            $this->getLogger()->debugMessage("---SENDING---\n$payload\n---END---");
1147
        }
1148
1149 322
        return $curl;
1150
    }
1151
1152
    /**
1153
     * Send an array of requests and return an array of responses.
1154
     *
1155
     * Unless $this->no_multicall has been set to true, it will try first to use one single xmlrpc call to server method
1156
     * system.multicall, and revert to sending many successive calls in case of failure.
1157
     * This failure is also stored in $this->no_multicall for subsequent calls.
1158
     * Unfortunately, there is no server error code universally used to denote the fact that multicall is unsupported,
1159
     * so there is no way to reliably distinguish between that and a temporary failure.
1160
     * If you are sure that server supports multicall and do not want to fallback to using many single calls, set the
1161
     * fourth parameter to FALSE.
1162
     *
1163
     * NB: trying to shoehorn extra functionality into existing syntax has resulted
1164
     * in pretty much convoluted code...
1165
     *
1166
     * @param Request[] $reqs an array of Request objects
1167
     * @param integer $timeout connection timeout (in seconds). See the details in the docs for the send() method
1168
     * @param string $method the http protocol variant to be used. See the details in the docs for the send() method
1169
     * @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...
1170
     *                         attempted
1171
     *
1172
     * @return Response[]
1173
     */
1174 65
    public function multicall($reqs, $timeout = 0, $method = '', $fallback = true)
1175
    {
1176 65
        if ($method == '') {
1177
            $method = $this->method;
1178
        }
1179 65
        if (!$this->no_multicall) {
1180 44
            $results = $this->_try_multicall($reqs, $timeout, $method);
1181 44
            if (is_array($results)) {
1182
                // System.multicall succeeded
1183 44
                return $results;
1184
            } else {
1185
                // either system.multicall is unsupported by server,
1186
                // or call failed for some other reason.
1187
                if ($fallback) {
1188
                    // Don't try it next time...
1189
                    $this->no_multicall = true;
1190
                } else {
1191
                    if (is_a($results, '\PhpXmlRpc\Response')) {
1192
                        $result = $results;
1193
                    } else {
1194
                        $result = new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'], PhpXmlRpc::$xmlrpcstr['multicall_error']);
1195
                    }
1196
                }
1197
            }
1198
        } else {
1199
            // override fallback, in case careless user tries to do two
1200
            // opposite things at the same time
1201 22
            $fallback = true;
1202
        }
1203
1204 22
        $results = array();
1205 22
        if ($fallback) {
1206
            // system.multicall is (probably) unsupported by server:
1207
            // emulate multicall via multiple requests
1208
            /// @todo use curl multi_ functions to make this quicker
1209 22
            foreach ($reqs as $req) {
1210 22
                $results[] = $this->send($req, $timeout, $method);
1211
            }
1212
        } else {
1213
            // user does NOT want to fallback on many single calls:
1214
            // since we should always return an array of responses,
1215
            // return an array with the same error repeated n times
1216
            foreach ($reqs as $req) {
1217
                $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...
1218
            }
1219
        }
1220
1221 22
        return $results;
1222
    }
1223
1224
    /**
1225
     * Attempt to boxcar $reqs via system.multicall.
1226
     *
1227
     * Returns either an array of Response, a single error Response or false (when received response does not respect
1228
     * valid multicall syntax).
1229
     *
1230
     * @param Request[] $reqs
1231
     * @param int $timeout
1232
     * @param string $method
1233
     * @return Response[]|false|mixed|Response
1234
     */
1235 44
    private function _try_multicall($reqs, $timeout, $method)
1236
    {
1237
        // Construct multicall request
1238 44
        $calls = array();
1239 44
        foreach ($reqs as $req) {
1240 44
            $call['methodName'] = new Value($req->method(), 'string');
1241 44
            $numParams = $req->getNumParams();
1242 44
            $params = array();
1243 44
            for ($i = 0; $i < $numParams; $i++) {
1244 44
                $params[$i] = $req->getParam($i);
1245
            }
1246 44
            $call['params'] = new Value($params, 'array');
1247 44
            $calls[] = new Value($call, 'struct');
1248
        }
1249 44
        $multiCall = new Request('system.multicall');
1250 44
        $multiCall->addParam(new Value($calls, 'array'));
1251
1252
        // Attempt RPC call
1253 44
        $result = $this->send($multiCall, $timeout, $method);
1254
1255 44
        if ($result->faultCode() != 0) {
1256
            // call to system.multicall failed
1257
            return $result;
1258
        }
1259
1260
        // Unpack responses.
1261 44
        $rets = $result->value();
1262
1263 44
        if ($this->return_type == 'xml') {
1264
            return $rets;
1265 44
        } elseif ($this->return_type == 'phpvals') {
1266
            /// @todo test this code branch...
1267 22
            $rets = $result->value();
1268 22
            if (!is_array($rets)) {
0 ignored issues
show
introduced by
The condition is_array($rets) is always false.
Loading history...
1269
                return false;       // bad return type from system.multicall
1270
            }
1271 22
            $numRets = count($rets);
1272 22
            if ($numRets != count($reqs)) {
1273
                return false;       // wrong number of return values.
1274
            }
1275
1276 22
            $response = array();
1277 22
            for ($i = 0; $i < $numRets; $i++) {
1278 22
                $val = $rets[$i];
1279 22
                if (!is_array($val)) {
1280
                    return false;
1281
                }
1282 22
                switch (count($val)) {
1283 22
                    case 1:
1284 22
                        if (!isset($val[0])) {
1285
                            return false;       // Bad value
1286
                        }
1287
                        // Normal return value
1288 22
                        $response[$i] = new Response($val[0], 0, '', 'phpvals');
1289 22
                        break;
1290 22
                    case 2:
1291
                        /// @todo remove usage of @: it is apparently quite slow
1292 22
                        $code = @$val['faultCode'];
1293 22
                        if (!is_int($code)) {
1294
                            return false;
1295
                        }
1296 22
                        $str = @$val['faultString'];
1297 22
                        if (!is_string($str)) {
1298
                            return false;
1299
                        }
1300 22
                        $response[$i] = new Response(0, $code, $str);
1301 22
                        break;
1302
                    default:
1303
                        return false;
1304
                }
1305
            }
1306
1307 22
            return $response;
1308
        } else {
1309
            // return type == 'xmlrpcvals'
1310
1311 23
            $rets = $result->value();
1312 23
            if ($rets->kindOf() != 'array') {
0 ignored issues
show
Bug introduced by
The method kindOf() does not exist on integer. ( Ignorable by Annotation )

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

1312
            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...
1313
                return false;       // bad return type from system.multicall
1314
            }
1315 23
            $numRets = $rets->count();
1316 23
            if ($numRets != count($reqs)) {
1317
                return false;       // wrong number of return values.
1318
            }
1319
1320 23
            $response = array();
1321 23
            foreach($rets as $val) {
0 ignored issues
show
Bug introduced by
The expression $rets of type integer is not traversable.
Loading history...
1322 23
                switch ($val->kindOf()) {
1323 23
                    case 'array':
1324 23
                        if ($val->count() != 1) {
1325
                            return false;       // Bad value
1326
                        }
1327
                        // Normal return value
1328 23
                        $response[] = new Response($val[0]);
1329 23
                        break;
1330 22
                    case 'struct':
1331 22
                        $code = $val['faultCode'];
1332
                        /** @var Value $code */
1333 22
                        if ($code->kindOf() != 'scalar' || $code->scalartyp() != 'int') {
1334
                            return false;
1335
                        }
1336 22
                        $str = $val['faultString'];
1337
                        /** @var Value $str */
1338 22
                        if ($str->kindOf() != 'scalar' || $str->scalartyp() != 'string') {
1339
                            return false;
1340
                        }
1341 22
                        $response[] = new Response(0, $code->scalarval(), $str->scalarval());
1342 22
                        break;
1343
                    default:
1344
                        return false;
1345
                }
1346
            }
1347
1348 23
            return $response;
1349
        }
1350
    }
1351
}
1352