Completed
Push — master ( 66f3e0...c4596e )
by Gaetano
07:18
created

Client::sendPayloadHTTP10()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 13
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0

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
    /// @todo: do these need to be public?
13
    public $method = 'http';
14
    public $server;
15
    public $port = 0;
16
    public $path;
17
18
    public $errno;
19
    public $errstr;
20
    public $debug = 0;
21
22
    public $username = '';
23
    public $password = '';
24
    public $authtype = 1;
25
26
    public $cert = '';
27
    public $certpass = '';
28
    public $cacert = '';
29
    public $cacertdir = '';
30
    public $key = '';
31
    public $keypass = '';
32
    public $verifypeer = true;
33
    public $verifyhost = 2;
34
    public $sslversion = 0; // corresponds to CURL_SSLVERSION_DEFAULT
35
36
    public $proxy = '';
37
    public $proxyport = 0;
38
    public $proxy_user = '';
39
    public $proxy_pass = '';
40
    public $proxy_authtype = 1;
41
42
    public $cookies = array();
43
    public $extracurlopts = array();
44
45
    /**
46
     * @var bool
47
     *
48
     * This determines whether the multicall() method will try to take advantage of the system.multicall xmlrpc method
49
     * to dispatch to the server an array of requests in a single http roundtrip or simply execute many consecutive http
50
     * calls. Defaults to FALSE, but it will be enabled automatically on the first failure of execution of
51
     * system.multicall.
52
     */
53
    public $no_multicall = false;
54
55
    /**
56
     * List of http compression methods accepted by the client for responses.
57
     * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib.
58
     *
59
     * NNB: you can set it to any non-empty array for HTTP11 and HTTPS, since
60
     * in those cases it will be up to CURL to decide the compression methods
61
     * it supports. You might check for the presence of 'zlib' in the output of
62
     * curl_version() to determine wheter compression is supported or not
63
     */
64
    public $accepted_compression = array();
65
66
    /**
67
     * Name of compression scheme to be used for sending requests.
68
     * Either null, gzip or deflate.
69
     */
70
71
    public $request_compression = '';
72
    /**
73
     * CURL handle: used for keep-alive connections (PHP 4.3.8 up, see:
74
     * http://curl.haxx.se/docs/faq.html#7.3).
75
     */
76
    public $xmlrpc_curl_handle = null;
77
78
    /// Whether to use persistent connections for http 1.1 and https
79
    public $keepalive = false;
80
81
    /// Charset encodings that can be decoded without problems by the client
82
    public $accepted_charset_encodings = array();
83
84
    /**
85
     * The charset encoding that will be used for serializing request sent by the client.
86
     * It defaults to NULL, which means using US-ASCII and encoding all characters outside of the ASCII range using
87
     * their xml character entity representation (this has the benefit that line end characters will not be mangled in
88
     * the transfer, a CR-LF will be preserved as well as a singe LF).
89
     *  Valid values are 'US-ASCII', 'UTF-8' and 'ISO-8859-1'
90
     */
91
    public $request_charset_encoding = '';
92
93
    /**
94
     * Decides the content of Response objects returned by calls to send() and multicall().
95
     * Valid values are 'xmlrpcvals', 'phpvals' or 'xml'.
96
     *
97
     * Determines whether the value returned inside an Response object as results of calls to the send() and multicall()
98
     * methods will be a Value object, a plain php value or a raw xml string.
99
     * Allowed values are 'xmlrpcvals' (the default), 'phpvals' and 'xml'.
100
     * To allow the user to differentiate between a correct and a faulty response, fault responses will be returned as
101
     * Response objects in any case.
102
     * Note that the 'phpvals' setting will yield faster execution times, but some of the information from the original
103
     * response will be lost. It will be e.g. impossible to tell whether a particular php string value was sent by the
104
     * server as an xmlrpc string or base64 value.
105
     */
106
    public $return_type = 'xmlrpcvals';
107
108
    /**
109
     * Sent to servers in http headers.
110
     */
111
    public $user_agent;
112
113
    /**
114
     * @param string $path either the PATH part of the xmlrpc server URL, or complete server URL (in which case you
115
     *                     should use and empty string for all other parameters)
116
     *                     e.g. /xmlrpc/server.php
117
     *                     e.g. http://phpxmlrpc.sourceforge.net/server.php
118
     *                     e.g. https://james:[email protected]:443/xmlrpcserver?agent=007
119
     * @param string $server the server name / ip address
120
     * @param integer $port the port the server is listening on, when omitted defaults to 80 or 443 depending on
121
     *                      protocol used
122
     * @param string $method the http protocol variant: defaults to 'http'; 'https' and 'http11' can be used if CURL is
123
     *                       installed. The value set here can be overridden in any call to $this->send().
124
     */
125
    public function __construct($path, $server = '', $port = '', $method = '')
126
    {
127
        // allow user to specify all params in $path
128
        if ($server == '' and $port == '' and $method == '') {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
129
            $parts = parse_url($path);
130
            $server = $parts['host'];
131
            $path = isset($parts['path']) ? $parts['path'] : '';
132
            if (isset($parts['query'])) {
133
                $path .= '?' . $parts['query'];
134
            }
135
            if (isset($parts['fragment'])) {
136
                $path .= '#' . $parts['fragment'];
137
            }
138
            if (isset($parts['port'])) {
139
                $port = $parts['port'];
140
            }
141
            if (isset($parts['scheme'])) {
142
                $method = $parts['scheme'];
143
            }
144
            if (isset($parts['user'])) {
145
                $this->username = $parts['user'];
146
            }
147
            if (isset($parts['pass'])) {
148
                $this->password = $parts['pass'];
149
            }
150
        }
151
        if ($path == '' || $path[0] != '/') {
152
            $this->path = '/' . $path;
153
        } else {
154
            $this->path = $path;
155
        }
156
        $this->server = $server;
157
        if ($port != '') {
158
            $this->port = $port;
159
        }
160
        if ($method != '') {
161
            $this->method = $method;
162
        }
163
164
        // if ZLIB is enabled, let the client by default accept compressed responses
165
        if (function_exists('gzinflate') || (
166
                function_exists('curl_init') && (($info = curl_version()) &&
167
                    ((is_string($info) && strpos($info, 'zlib') !== null) || isset($info['libz_version'])))
168
            )
169
        ) {
170
            $this->accepted_compression = array('gzip', 'deflate');
171
        }
172
173
        // keepalives: enabled by default
174
        $this->keepalive = true;
175
176
        // by default the xml parser can support these 3 charset encodings
177
        $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII');
178
179
        // Add all charsets which mbstring can handle, but remove junk not found in IANA registry at
180
        // in http://www.iana.org/assignments/character-sets/character-sets.xhtml
181
        // NB: this is disabled to avoid making all the requests sent huge... mbstring supports more than 80 charsets!
182
        /*if (function_exists('mb_list_encodings')) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
63% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
183
184
            $encodings = array_diff(mb_list_encodings(), array('pass', 'auto', 'wchar', 'BASE64', 'UUENCODE', 'ASCII',
185
                'HTML-ENTITIES', 'Quoted-Printable', '7bit','8bit', 'byte2be', 'byte2le', 'byte4be', 'byte4le'));
186
            $this->accepted_charset_encodings = array_unique(array_merge($this->accepted_charset_encodings, $encodings));
187
        }*/
188
189
        // initialize user_agent string
190
        $this->user_agent = PhpXmlRpc::$xmlrpcName . ' ' . PhpXmlRpc::$xmlrpcVersion;
191
    }
192
193
    /**
194
     * Enable/disable the echoing to screen of the xmlrpc responses received. The default is not no output anything.
195
     *
196
     * The debugging information at level 1 includes the raw data returned from the XML-RPC server it was querying
197
     * (including bot HTTP headers and the full XML payload), and the PHP value the client attempts to create to
198
     * represent the value returned by the server
199
     * At level2, the complete payload of the xmlrpc request is also printed, before being sent t the server.
200
     *
201
     * This option can be very useful when debugging servers as it allows you to see exactly what the client sends and
202
     * the server returns.
203
     *
204
     * @param integer $in values 0, 1 and 2 are supported (2 = echo sent msg too, before received response)
0 ignored issues
show
Bug introduced by
There is no parameter named $in. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
205
     */
206
    public function setDebug($level)
207
    {
208
        $this->debug = $level;
209
    }
210
211
    /**
212
     * Sets the username and password for authorizing the client to the server.
213
     *
214
     * With the default (HTTP) transport, this information is used for HTTP Basic authorization.
215
     * Note that username and password can also be set using the class constructor.
216
     * With HTTP 1.1 and HTTPS transport, NTLM and Digest authentication protocols are also supported. To enable them use
217
     * the constants CURLAUTH_DIGEST and CURLAUTH_NTLM as values for the auth type parameter.
218
     *
219
     * @param string $user username
220
     * @param string $password password
221
     * @param integer $authType auth type. See curl_setopt man page for supported auth types. Defaults to CURLAUTH_BASIC
222
     *                          (basic auth). Note that auth types NTLM and Digest will only work if the Curl php
223
     *                          extension is enabled.
224
     */
225
    public function setCredentials($user, $password, $authType = 1)
226
    {
227
        $this->username = $user;
228
        $this->password = $password;
229
        $this->authtype = $authType;
230
    }
231
232
    /**
233
     * Set the optional certificate and passphrase used in SSL-enabled communication with a remote server.
234
     *
235
     * Note: to retrieve information about the client certificate on the server side, you will need to look into the
236
     * environment variables which are set up by the webserver. Different webservers will typically set up different
237
     * variables.
238
     *
239
     * @param string $cert the name of a file containing a PEM formatted certificate
240
     * @param string $certPass the password required to use it
241
     */
242
    public function setCertificate($cert, $certPass = '')
243
    {
244
        $this->cert = $cert;
245
        $this->certpass = $certPass;
246
    }
247
248
    /**
249
     * Add a CA certificate to verify server with in SSL-enabled communication when SetSSLVerifypeer has been set to TRUE.
250
     *
251
     * See the php manual page about CURLOPT_CAINFO for more details.
252
     *
253
     * @param string $caCert certificate file name (or dir holding certificates)
254
     * @param bool $isDir set to true to indicate cacert is a dir. defaults to false
255
     */
256
    public function setCaCertificate($caCert, $isDir = false)
257
    {
258
        if ($isDir) {
259
            $this->cacertdir = $caCert;
260
        } else {
261
            $this->cacert = $caCert;
262
        }
263
    }
264
265
    /**
266
     * Set attributes for SSL communication: private SSL key.
267
     *
268
     * NB: does not work in older php/curl installs.
269
     * Thanks to Daniel Convissor.
270
     *
271
     * @param string $key The name of a file containing a private SSL key
272
     * @param string $keyPass The secret password needed to use the private SSL key
273
     */
274
    public function setKey($key, $keyPass)
275
    {
276
        $this->key = $key;
277
        $this->keypass = $keyPass;
278
    }
279
280
    /**
281
     * Set attributes for SSL communication: verify the remote host's SSL certificate, and cause the connection to fail
282
     * if the cert verification fails.
283
     *
284
     * By default, verification is enabled.
285
     * To specify custom SSL certificates to validate the server with, use the setCaCertificate method.
286
     *
287
     * @param bool $i enable/disable verification of peer certificate
288
     */
289
    public function setSSLVerifyPeer($i)
290
    {
291
        $this->verifypeer = $i;
292
    }
293
294
    /**
295
     * Set attributes for SSL communication: verify the remote host's SSL certificate's common name (CN).
296
     *
297
     * Note that support for value 1 has been removed in cURL 7.28.1
298
     *
299
     * @param int $i Set to 1 to only the existence of a CN, not that it matches
300
     */
301
    public function setSSLVerifyHost($i)
302
    {
303
        $this->verifyhost = $i;
304
    }
305
306
    /**
307
     * Set attributes for SSL communication: SSL version to use. Best left at 0 (default value ): let cURL decide
308
     *
309
     * @param int $i
310
     */
311
    public function setSSLVersion($i)
312
    {
313
        $this->sslversion = $i;
314
    }
315
316
    /**
317
     * Set proxy info.
318
     *
319
     * NB: CURL versions before 7.11.10 cannot use a proxy to communicate with https servers.
320
     *
321
     * @param string $proxyHost
322
     * @param string $proxyPort Defaults to 8080 for HTTP and 443 for HTTPS
323
     * @param string $proxyUsername Leave blank if proxy has public access
324
     * @param string $proxyPassword Leave blank if proxy has public access
325
     * @param int $proxyAuthType defaults to CURLAUTH_BASIC (Basic authentication protocol); set to constant CURLAUTH_NTLM
326
     *                           to use NTLM auth with proxy (has effect only when the client uses the HTTP 1.1 protocol)
327
     */
328
    public function setProxy($proxyHost, $proxyPort, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1)
329
    {
330
        $this->proxy = $proxyHost;
331
        $this->proxyport = $proxyPort;
0 ignored issues
show
Documentation Bug introduced by
The property $proxyport was declared of type integer, but $proxyPort is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
332
        $this->proxy_user = $proxyUsername;
333
        $this->proxy_pass = $proxyPassword;
334
        $this->proxy_authtype = $proxyAuthType;
335
    }
336
337
    /**
338
     * Enables/disables reception of compressed xmlrpc responses.
339
     *
340
     * This requires the "zlib" extension to be enabled in your php install. If it is, by default xmlrpc_client
341
     * instances will enable reception of compressed content.
342
     * Note that enabling reception of compressed responses merely adds some standard http headers to xmlrpc requests.
343
     * It is up to the xmlrpc server to return compressed responses when receiving such requests.
344
     *
345
     * @param string $compMethod either 'gzip', 'deflate', 'any' or ''
346
     */
347
    public function setAcceptedCompression($compMethod)
348
    {
349
        if ($compMethod == 'any') {
350
            $this->accepted_compression = array('gzip', 'deflate');
351
        } 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...
352
            $this->accepted_compression = array();
353
        } else {
354
            $this->accepted_compression = array($compMethod);
355
        }
356
    }
357
358
    /**
359
     * Enables/disables http compression of xmlrpc request.
360
     *
361
     * This requires the "zlib" extension to be enabled in your php install.
362
     * Take care when sending compressed requests: servers might not support them (and automatic fallback to
363
     * uncompressed requests is not yet implemented).
364
     *
365
     * @param string $compMethod either 'gzip', 'deflate' or ''
366
     */
367
    public function setRequestCompression($compMethod)
368
    {
369
        $this->request_compression = $compMethod;
370
    }
371
372
    /**
373
     * Adds a cookie to list of cookies that will be sent to server with every further request (useful e.g. for keeping
374
     * session info outside of the xml-rpc payload).
375
     *
376
     * NB: By default cookies are sent using the 'original/netscape' format, which is also the same as the RFC 2965;
377
     * setting any param but name and value will turn the cookie into a 'version 1' cookie (i.e. RFC 2109 cookie) that
378
     * might not be fully supported by the server. Note that RFC 2109 has currently 'historic' status...
379
     *
380
     * @param string $name nb: will not be escaped in the request's http headers. Take care not to use CTL chars or
381
     *                     separators!
382
     * @param string $value
383
     * @param string $path leave this empty unless the xml-rpc server only accepts RFC 2109 cookies
384
     * @param string $domain leave this empty unless the xml-rpc server only accepts RFC 2109 cookies
385
     * @param int $port leave this empty unless the xml-rpc server only accepts RFC 2109 cookies
386
     *
387
     * @todo check correctness of urlencoding cookie value (copied from php way of doing it, but php is generally sending
388
     *       response not requests. We do the opposite...)
389
     * @todo strip invalid chars from cookie name? As per RFC6265, we should follow RFC2616, Section 2.2
390
     */
391
    public function setCookie($name, $value = '', $path = '', $domain = '', $port = null)
392
    {
393
        $this->cookies[$name]['value'] = urlencode($value);
394
        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 zero. 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...
395
            $this->cookies[$name]['path'] = $path;
396
            $this->cookies[$name]['domain'] = $domain;
397
            $this->cookies[$name]['port'] = $port;
398
            $this->cookies[$name]['version'] = 1;
399
        } else {
400
            $this->cookies[$name]['version'] = 0;
401
        }
402
    }
403
404
    /**
405
     * Directly set cURL options, for extra flexibility (when in cURL mode).
406
     *
407
     * It allows eg. to bind client to a specific IP interface / address.
408
     *
409
     * @param array $options
410
     */
411
    public function setCurlOptions($options)
412
    {
413
        $this->extracurlopts = $options;
414
    }
415
416
    /**
417
     * Set user-agent string that will be used by this client instance in http headers sent to the server.
418
     *
419
     * The default user agent string includes the name of this library and the version number.
420
     *
421
     * @param string $agentString
422
     */
423
    public function setUserAgent($agentString)
424
    {
425
        $this->user_agent = $agentString;
426
    }
427
428
    /**
429
     * Send an xmlrpc request to the server.
430
     *
431
     * @param Request|Request[]|string $req The Request object, or an array of requests for using multicall, or the
432
     *                                      complete xml representation of a request.
433
     *                                      When sending an array of Request objects, the client will try to make use of
434
     *                                      a single 'system.multicall' xml-rpc method call to forward to the server all
435
     *                                      the requests in a single HTTP round trip, unless $this->no_multicall has
436
     *                                      been previously set to TRUE (see the multicall method below), in which case
437
     *                                      many consecutive xmlrpc requests will be sent. The method will return an
438
     *                                      array of Response objects in both cases.
439
     *                                      The third variant allows to build by hand (or any other means) a complete
440
     *                                      xmlrpc request message, and send it to the server. $req should be a string
441
     *                                      containing the complete xml representation of the request. It is e.g. useful
442
     *                                      when, for maximal speed of execution, the request is serialized into a
443
     *                                      string using the native php xmlrpc functions (see http://www.php.net/xmlrpc)
444
     * @param integer $timeout Connection timeout, in seconds, If unspecified, a platform specific timeout will apply.
445
     *                         This timeout value is passed to fsockopen(). It is also used for detecting server
446
     *                         timeouts during communication (i.e. if the server does not send anything to the client
447
     *                         for $timeout seconds, the connection will be closed).
448
     * @param string $method valid values are 'http', 'http11' and 'https'. If left unspecified, the http protocol
449
     *                       chosen during creation of the object will be used.
450
     *
451
     * @return Response|Response[] Note that the client will always return a Response object, even if the call fails
452
     */
453
    public function send($req, $timeout = 0, $method = '')
454
    {
455
        // if user does not specify http protocol, use native method of this client
456
        // (i.e. method set during call to constructor)
457
        if ($method == '') {
458
            $method = $this->method;
459
        }
460
461
        if (is_array($req)) {
462
            // $req is an array of Requests
463
            $r = $this->multicall($req, $timeout, $method);
464
465
            return $r;
466
        } elseif (is_string($req)) {
467
            $n = new Request('');
468
            $n->payload = $req;
469
            $req = $n;
470
        }
471
472
        // where req is a Request
473
        $req->setDebug($this->debug);
474
475
        /// @todo we could be smarter about this and force usage of curl in scenarios where it is both available and
476
        ///       needed, such as digest or ntlm auth
477
        $useCurl = ($method == 'https' || $method == 'http11');
478
479
        if ($useCurl) {
480
            $r = $this->sendPayloadCURL(
481
                $req,
482
                $this->server,
483
                $this->port,
484
                $timeout,
485
                $this->username,
486
                $this->password,
487
                $this->authtype,
488
                $this->cert,
489
                $this->certpass,
490
                $this->cacert,
491
                $this->cacertdir,
492
                $this->proxy,
493
                $this->proxyport,
494
                $this->proxy_user,
495
                $this->proxy_pass,
496
                $this->proxy_authtype,
497
                // bc
498
                $method == 'http11' ? 'http' : $method,
499
                $this->keepalive,
500
                $this->key,
501
                $this->keypass,
502
                $this->sslversion
503
            );
504
        } else {
505
            // plain 'http 1.0': default to using socket
506
            $r = $this->sendPayloadSocket(
507
                $req,
508
                $this->server,
509
                $this->port,
510
                $timeout,
511
                $this->username,
512
                $this->password,
513
                $this->authtype,
514
                $this->cert,
515
                $this->certpass,
516
                $this->cacert,
517
                $this->cacertdir,
518
                $this->proxy,
519
                $this->proxyport,
520
                $this->proxy_user,
521
                $this->proxy_pass,
522
                $this->proxy_authtype,
523
                $method,
524
                $this->keepalive,
0 ignored issues
show
Documentation introduced by
$this->keepalive is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
525
                $this->key,
526
                $this->keypass,
527
                $this->sslversion
0 ignored issues
show
Unused Code introduced by
The call to Client::sendPayloadSocket() has too many arguments starting with $this->sslversion.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
528
            );
529
        }
530
531
        return $r;
532
    }
533
534
    /**
535
     * @deprecated
536
     * @param Request $req
537
     * @param string $server
538
     * @param int $port
539
     * @param int $timeout
540
     * @param string $username
541
     * @param string $password
542
     * @param int $authType
543
     * @param string $proxyHost
544
     * @param int $proxyPort
545
     * @param string $proxyUsername
546
     * @param string $proxyPassword
547
     * @param int $proxyAuthType
548
     * @param string $method
549
     * @return Response
550
     */
551
    protected function sendPayloadHTTP10($req, $server, $port, $timeout = 0, $username = '', $password = '',
552
        $authType = 1, $proxyHost = '', $proxyPort = 0, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1,
553
        $method='http')
0 ignored issues
show
Unused Code introduced by
The parameter $method is not used and could be removed.

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

Loading history...
554
    {
555
        return $this->sendPayloadSocket($req, $server, $port, $timeout, $username, $password, $authType, null, null,
556
            null, null, $proxyHost, $proxyPort, $proxyUsername, $proxyPassword, $proxyAuthType);
557
    }
558
559
    /**
560
     * @deprecated
561
     * @param Request $req
562
     * @param string $server
563
     * @param int $port
564
     * @param int $timeout
565
     * @param string $username
566
     * @param string $password
567
     * @param int $authType
568
     * @param string $cert
569
     * @param string $certPass
570
     * @param string $caCert
571
     * @param string $caCertDir
572
     * @param string $proxyHost
573
     * @param int $proxyPort
574
     * @param string $proxyUsername
575
     * @param string $proxyPassword
576
     * @param int $proxyAuthType
577
     * @param bool $keepAlive
578
     * @param string $key
579
     * @param string $keyPass
580
     * @param int $sslVersion
581
     * @return Response
582
     */
583
    protected function sendPayloadHTTPS($req, $server, $port, $timeout = 0, $username = '',  $password = '',
584
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
585
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $keepAlive = false, $key = '', $keyPass = '',
586
        $sslVersion = 0)
587
    {
588
        return $this->sendPayloadCURL($req, $server, $port, $timeout, $username,
589
            $password, $authType, $cert, $certPass, $caCert, $caCertDir, $proxyHost, $proxyPort,
590
            $proxyUsername, $proxyPassword, $proxyAuthType, 'https', $keepAlive, $key, $keyPass, $sslVersion);
591
    }
592
593
    /**
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 only value supported is 1
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 only value supported is 1
610
     * @param string $method 'http' (synonym for 'http10'), 'http10' or 'https'
611
     * @param string $key
612
     * @param string $keyPass @todo not implemented yet.
613
     * @param int $sslVersion @todo not implemented yet. See http://php.net/manual/en/migration56.openssl.php
614
     * @return Response
615
     */
616
    protected function sendPayloadSocket($req, $server, $port, $timeout = 0, $username = '', $password = '',
617
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
618
        $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.

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

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

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

Loading history...
620
    {
621
        if ($port == 0) {
622
            $port = ( $method === 'https' ) ? 443 : 80;
623
        }
624
625
        // Only create the payload if it was not created previously
626
        if (empty($req->payload)) {
627
            $req->createPayload($this->request_charset_encoding);
628
        }
629
630
        $payload = $req->payload;
631
        // Deflate request body and set appropriate request headers
632 View Code Duplication
        if (function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
633
            if ($this->request_compression == 'gzip') {
634
                $a = @gzencode($payload);
635
                if ($a) {
636
                    $payload = $a;
637
                    $encodingHdr = "Content-Encoding: gzip\r\n";
638
                }
639
            } else {
640
                $a = @gzcompress($payload);
641
                if ($a) {
642
                    $payload = $a;
643
                    $encodingHdr = "Content-Encoding: deflate\r\n";
644
                }
645
            }
646
        } else {
647
            $encodingHdr = '';
648
        }
649
650
        // thanks to Grant Rauscher <[email protected]> for this
651
        $credentials = '';
652
        if ($username != '') {
653
            $credentials = 'Authorization: Basic ' . base64_encode($username . ':' . $password) . "\r\n";
654
            if ($authType != 1) {
655
                error_log('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported with HTTP 1.0');
656
            }
657
        }
658
659
        $acceptedEncoding = '';
660
        if (is_array($this->accepted_compression) && count($this->accepted_compression)) {
661
            $acceptedEncoding = 'Accept-Encoding: ' . implode(', ', $this->accepted_compression) . "\r\n";
662
        }
663
664
        $proxyCredentials = '';
665
        if ($proxyHost) {
666
            if ($proxyPort == 0) {
667
                $proxyPort = 8080;
668
            }
669
            $connectServer = $proxyHost;
670
            $connectPort = $proxyPort;
671
            $transport = 'tcp';
672
            $uri = 'http://' . $server . ':' . $port . $this->path;
673
            if ($proxyUsername != '') {
674
                if ($proxyAuthType != 1) {
675
                    error_log('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported with HTTP 1.0');
676
                }
677
                $proxyCredentials = 'Proxy-Authorization: Basic ' . base64_encode($proxyUsername . ':' . $proxyPassword) . "\r\n";
678
            }
679
        } else {
680
            $connectServer = $server;
681
            $connectPort = $port;
682
            /// @todo if supporting https, we should support all its current options as well: peer name verification etc...
683
            $transport = ( $method === 'https' ) ? 'tls' : 'tcp';
684
            $uri = $this->path;
685
        }
686
687
        // Cookie generation, as per rfc2965 (version 1 cookies) or
688
        // netscape's rules (version 0 cookies)
689
        $cookieHeader = '';
690
        if (count($this->cookies)) {
691
            $version = '';
692
            foreach ($this->cookies as $name => $cookie) {
693
                if ($cookie['version']) {
694
                    $version = ' $Version="' . $cookie['version'] . '";';
695
                    $cookieHeader .= ' ' . $name . '="' . $cookie['value'] . '";';
696
                    if ($cookie['path']) {
697
                        $cookieHeader .= ' $Path="' . $cookie['path'] . '";';
698
                    }
699
                    if ($cookie['domain']) {
700
                        $cookieHeader .= ' $Domain="' . $cookie['domain'] . '";';
701
                    }
702
                    if ($cookie['port']) {
703
                        $cookieHeader .= ' $Port="' . $cookie['port'] . '";';
704
                    }
705
                } else {
706
                    $cookieHeader .= ' ' . $name . '=' . $cookie['value'] . ";";
707
                }
708
            }
709
            $cookieHeader = 'Cookie:' . $version . substr($cookieHeader, 0, -1) . "\r\n";
710
        }
711
712
        // omit port if default
713
        if (($port == 80 && in_array($method, array('http', 'http10'))) || ($port == 443 && $method == 'https')) {
714
            $port =  '';
715
        } else {
716
            $port = ':' . $port;
717
        }
718
719
        $op = 'POST ' . $uri . " HTTP/1.0\r\n" .
720
            'User-Agent: ' . $this->user_agent . "\r\n" .
721
            'Host: ' . $server . $port . "\r\n" .
722
            $credentials .
723
            $proxyCredentials .
724
            $acceptedEncoding .
725
            $encodingHdr .
0 ignored issues
show
Bug introduced by
The variable $encodingHdr does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
726
            'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings) . "\r\n" .
727
            $cookieHeader .
728
            'Content-Type: ' . $req->content_type . "\r\nContent-Length: " .
729
            strlen($payload) . "\r\n\r\n" .
730
            $payload;
731
732
        if ($this->debug > 1) {
733
            Logger::instance()->debugMessage("---SENDING---\n$op\n---END---");
734
        }
735
736
        $contextOptions = array();
737
        if ($method == 'https') {
738
            if ($cert != '') {
739
                $contextOptions['ssl']['local_cert'] = $cert;
740
                if ($certPass != '') {
741
                    $contextOptions['ssl']['passphrase'] = $certPass;
742
                }
743
            }
744
            if ($caCert != '') {
745
                $contextOptions['ssl']['cafile'] = $caCert;
746
            }
747
            if ($caCertDir != '') {
748
                $contextOptions['ssl']['capath'] = $caCertDir;
749
            }
750
            if ($key != '') {
751
                $contextOptions['ssl']['local_pk'] = $key;
752
            }
753
            $contextOptions['ssl']['verify_peer'] = $this->verifypeer;
754
755
        }
756
        $context = stream_context_create($contextOptions);
757
758
        if ($timeout <= 0) {
759
            $connectTimeout = ini_get('default_socket_timeout');
760
        } else {
761
            $connectTimeout = $timeout;
762
        }
763
764
        $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr, $connectTimeout,
765
            STREAM_CLIENT_CONNECT, $context);
766
        if ($fp) {
767
            if ($timeout > 0) {
768
                stream_set_timeout($fp, $timeout);
769
            }
770
        } else {
771
            $this->errstr = 'Connect error: ' . $this->errstr;
772
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr . ' (' . $this->errno . ')');
773
774
            return $r;
775
        }
776
777
        if (!fputs($fp, $op, strlen($op))) {
778
            fclose($fp);
779
            $this->errstr = 'Write error';
780
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr);
781
782
            return $r;
783
        } else {
784
            // reset errno and errstr on successful socket connection
785
            $this->errstr = '';
786
        }
787
        // G. Giunta 2005/10/24: close socket before parsing.
788
        // should yield slightly better execution times, and make easier recursive calls (e.g. to follow http redirects)
789
        $ipd = '';
790
        do {
791
            // shall we check for $data === FALSE?
792
            // as per the manual, it signals an error
793
            $ipd .= fread($fp, 32768);
794
        } while (!feof($fp));
795
        fclose($fp);
796
        $r = $req->parseResponse($ipd, false, $this->return_type);
797
798
        return $r;
799
    }
800
801
    /**
802
     * Contributed by Justin Miller <[email protected]>
803
     * Requires curl to be built into PHP
804
     * NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers!
805
     *
806
     * @param Request $req
807
     * @param string $server
808
     * @param int $port
809
     * @param int $timeout
810
     * @param string $username
811
     * @param string $password
812
     * @param int $authType
813
     * @param string $cert
814
     * @param string $certPass
815
     * @param string $caCert
816
     * @param string $caCertDir
817
     * @param string $proxyHost
818
     * @param int $proxyPort
819
     * @param string $proxyUsername
820
     * @param string $proxyPassword
821
     * @param int $proxyAuthType
822
     * @param string $method 'http' (let curl decide), 'http10', 'http11' or 'https'
823
     * @param bool $keepAlive
824
     * @param string $key
825
     * @param string $keyPass
826
     * @param int $sslVersion
827
     * @return Response
828
     */
829
    protected function sendPayloadCURL($req, $server, $port, $timeout = 0, $username = '', $password = '',
830
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
831
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'https', $keepAlive = false, $key = '',
832
        $keyPass = '', $sslVersion = 0)
833
    {
834
        if (!function_exists('curl_init')) {
835
            $this->errstr = 'CURL unavailable on this install';
836
            return new Response(0, PhpXmlRpc::$xmlrpcerr['no_curl'], PhpXmlRpc::$xmlrpcstr['no_curl']);
837
        }
838
        if ($method == 'https') {
839
            if (($info = curl_version()) &&
840
                ((is_string($info) && strpos($info, 'OpenSSL') === null) || (is_array($info) && !isset($info['ssl_version'])))
841
            ) {
842
                $this->errstr = 'SSL unavailable on this install';
843
                return new Response(0, PhpXmlRpc::$xmlrpcerr['no_ssl'], PhpXmlRpc::$xmlrpcstr['no_ssl']);
844
            }
845
        }
846
847
        if ($port == 0) {
848
            if (in_array($method, array('http', 'http10', 'http11'))) {
849
                $port = 80;
850
            } else {
851
                $port = 443;
852
            }
853
        }
854
855
        // Only create the payload if it was not created previously
856
        if (empty($req->payload)) {
857
            $req->createPayload($this->request_charset_encoding);
858
        }
859
860
        // Deflate request body and set appropriate request headers
861
        $payload = $req->payload;
862 View Code Duplication
        if (function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
863
            if ($this->request_compression == 'gzip') {
864
                $a = @gzencode($payload);
865
                if ($a) {
866
                    $payload = $a;
867
                    $encodingHdr = 'Content-Encoding: gzip';
868
                }
869
            } else {
870
                $a = @gzcompress($payload);
871
                if ($a) {
872
                    $payload = $a;
873
                    $encodingHdr = 'Content-Encoding: deflate';
874
                }
875
            }
876
        } else {
877
            $encodingHdr = '';
878
        }
879
880
        if ($this->debug > 1) {
881
            Logger::instance()->debugMessage("---SENDING---\n$payload\n---END---");
882
        }
883
884
        if (!$keepAlive || !$this->xmlrpc_curl_handle) {
885
            if ($method == 'http11' || $method == 'http10') {
886
                $protocol = 'http';
887
            } else {
888
                $protocol = $method;
889
            }
890
            $curl = curl_init($protocol . '://' . $server . ':' . $port . $this->path);
891
            if ($keepAlive) {
892
                $this->xmlrpc_curl_handle = $curl;
893
            }
894
        } else {
895
            $curl = $this->xmlrpc_curl_handle;
896
        }
897
898
        // results into variable
899
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
900
901
        if ($this->debug > 1) {
902
            curl_setopt($curl, CURLOPT_VERBOSE, true);
903
            /// @todo allow callers to redirect curlopt_stderr to some stream which can be buffered
904
        }
905
        curl_setopt($curl, CURLOPT_USERAGENT, $this->user_agent);
906
        // required for XMLRPC: post the data
907
        curl_setopt($curl, CURLOPT_POST, 1);
908
        // the data
909
        curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
910
911
        // return the header too
912
        curl_setopt($curl, CURLOPT_HEADER, 1);
913
914
        // NB: if we set an empty string, CURL will add http header indicating
915
        // ALL methods it is supporting. This is possibly a better option than
916
        // letting the user tell what curl can / cannot do...
917
        if (is_array($this->accepted_compression) && count($this->accepted_compression)) {
918
            //curl_setopt($curl, CURLOPT_ENCODING, implode(',', $this->accepted_compression));
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
919
            // empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
920
            if (count($this->accepted_compression) == 1) {
921
                curl_setopt($curl, CURLOPT_ENCODING, $this->accepted_compression[0]);
922
            } else {
923
                curl_setopt($curl, CURLOPT_ENCODING, '');
924
            }
925
        }
926
        // extra headers
927
        $headers = array('Content-Type: ' . $req->content_type, 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings));
928
        // if no keepalive is wanted, let the server know it in advance
929
        if (!$keepAlive) {
930
            $headers[] = 'Connection: close';
931
        }
932
        // request compression header
933
        if ($encodingHdr) {
934
            $headers[] = $encodingHdr;
0 ignored issues
show
Bug introduced by
The variable $encodingHdr does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
935
        }
936
937
        // Fix the HTTP/1.1 417 Expectation Failed Bug (curl by default adds a 'Expect: 100-continue' header when POST
938
        // size exceeds 1025 bytes, apparently)
939
        $headers[] = 'Expect:';
940
941
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
942
        // timeout is borked
943
        if ($timeout) {
944
            curl_setopt($curl, CURLOPT_TIMEOUT, $timeout == 1 ? 1 : $timeout - 1);
945
        }
946
947
        if ($method == 'http10') {
948
            curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
949
        } elseif ($method == 'http11') {
950
            curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
951
        }
952
953 View Code Duplication
        if ($username && $password) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
954
            curl_setopt($curl, CURLOPT_USERPWD, $username . ':' . $password);
955
            if (defined('CURLOPT_HTTPAUTH')) {
956
                curl_setopt($curl, CURLOPT_HTTPAUTH, $authType);
957
            } elseif ($authType != 1) {
958
                error_log('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported by the current PHP/curl install');
959
            }
960
        }
961
962
        if ($method == 'https') {
963
            // set cert file
964
            if ($cert) {
965
                curl_setopt($curl, CURLOPT_SSLCERT, $cert);
966
            }
967
            // set cert password
968
            if ($certPass) {
969
                curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $certPass);
970
            }
971
            // whether to verify remote host's cert
972
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verifypeer);
973
            // set ca certificates file/dir
974
            if ($caCert) {
975
                curl_setopt($curl, CURLOPT_CAINFO, $caCert);
976
            }
977
            if ($caCertDir) {
978
                curl_setopt($curl, CURLOPT_CAPATH, $caCertDir);
979
            }
980
            // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
981
            if ($key) {
982
                curl_setopt($curl, CURLOPT_SSLKEY, $key);
983
            }
984
            // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
985
            if ($keyPass) {
986
                curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $keyPass);
987
            }
988
            // whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that it matches the hostname used
989
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $this->verifyhost);
990
            // allow usage of different SSL versions
991
            curl_setopt($curl, CURLOPT_SSLVERSION, $sslVersion);
992
        }
993
994
        // proxy info
995
        if ($proxyHost) {
996
            if ($proxyPort == 0) {
997
                $proxyPort = 8080; // NB: even for HTTPS, local connection is on port 8080
998
            }
999
            curl_setopt($curl, CURLOPT_PROXY, $proxyHost . ':' . $proxyPort);
1000 View Code Duplication
            if ($proxyUsername) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1001
                curl_setopt($curl, CURLOPT_PROXYUSERPWD, $proxyUsername . ':' . $proxyPassword);
1002
                if (defined('CURLOPT_PROXYAUTH')) {
1003
                    curl_setopt($curl, CURLOPT_PROXYAUTH, $proxyAuthType);
1004
                } elseif ($proxyAuthType != 1) {
1005
                    error_log('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported by the current PHP/curl install');
1006
                }
1007
            }
1008
        }
1009
1010
        // NB: should we build cookie http headers by hand rather than let CURL do it?
1011
        // the following code does not honour 'expires', 'path' and 'domain' cookie attributes
1012
        // set to client obj the the user...
1013
        if (count($this->cookies)) {
1014
            $cookieHeader = '';
1015
            foreach ($this->cookies as $name => $cookie) {
1016
                $cookieHeader .= $name . '=' . $cookie['value'] . '; ';
1017
            }
1018
            curl_setopt($curl, CURLOPT_COOKIE, substr($cookieHeader, 0, -2));
1019
        }
1020
1021
        foreach ($this->extracurlopts as $opt => $val) {
1022
            curl_setopt($curl, $opt, $val);
1023
        }
1024
1025
        $result = curl_exec($curl);
1026
1027
        if ($this->debug > 1) {
1028
            $message = "---CURL INFO---\n";
1029
            foreach (curl_getinfo($curl) as $name => $val) {
1030
                if (is_array($val)) {
1031
                    $val = implode("\n", $val);
1032
                }
1033
                $message .= $name . ': ' . $val . "\n";
1034
            }
1035
            $message .= '---END---';
1036
            Logger::instance()->debugMessage($message);
1037
        }
1038
1039
        if (!$result) {
1040
            /// @todo we should use a better check here - what if we get back '' or '0'?
1041
1042
            $this->errstr = 'no response';
1043
            $resp = new Response(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] . ': ' . curl_error($curl));
1044
            curl_close($curl);
1045
            if ($keepAlive) {
1046
                $this->xmlrpc_curl_handle = null;
1047
            }
1048
        } else {
1049
            if (!$keepAlive) {
1050
                curl_close($curl);
1051
            }
1052
            $resp = $req->parseResponse($result, true, $this->return_type);
1053
            // if we got back a 302, we can not reuse the curl handle for later calls
1054
            if ($resp->faultCode() == PhpXmlRpc::$xmlrpcerr['http_error'] && $keepAlive) {
1055
                curl_close($curl);
1056
                $this->xmlrpc_curl_handle = null;
1057
            }
1058
        }
1059
1060
        return $resp;
1061
    }
1062
1063
    /**
1064
     * Send an array of requests and return an array of responses.
1065
     *
1066
     * Unless $this->no_multicall has been set to true, it will try first to use one single xmlrpc call to server method
1067
     * system.multicall, and revert to sending many successive calls in case of failure.
1068
     * This failure is also stored in $this->no_multicall for subsequent calls.
1069
     * Unfortunately, there is no server error code universally used to denote the fact that multicall is unsupported,
1070
     * so there is no way to reliably distinguish between that and a temporary failure.
1071
     * If you are sure that server supports multicall and do not want to fallback to using many single calls, set the
1072
     * fourth parameter to FALSE.
1073
     *
1074
     * NB: trying to shoehorn extra functionality into existing syntax has resulted
1075
     * in pretty much convoluted code...
1076
     *
1077
     * @param Request[] $reqs an array of Request objects
1078
     * @param integer $timeout connection timeout (in seconds). See the details in the docs for the send() method
1079
     * @param string $method the http protocol variant to be used. See the details in the docs for the send() method
1080
     * @param boolean fallback When true, upon receiving an error during multicall, multiple single calls will be
1081
     *                         attempted
1082
     *
1083
     * @return Response[]
1084
     */
1085
    public function multicall($reqs, $timeout = 0, $method = '', $fallback = true)
1086
    {
1087
        if ($method == '') {
1088
            $method = $this->method;
1089
        }
1090
        if (!$this->no_multicall) {
1091
            $results = $this->_try_multicall($reqs, $timeout, $method);
1092
            if (is_array($results)) {
1093
                // System.multicall succeeded
1094
                return $results;
1095
            } else {
1096
                // either system.multicall is unsupported by server,
1097
                // or call failed for some other reason.
1098 View Code Duplication
                if ($fallback) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1099
                    // Don't try it next time...
1100
                    $this->no_multicall = true;
1101
                } else {
1102
                    if (is_a($results, '\PhpXmlRpc\Response')) {
1103
                        $result = $results;
1104
                    } else {
1105
                        $result = new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'], PhpXmlRpc::$xmlrpcstr['multicall_error']);
1106
                    }
1107
                }
1108
            }
1109
        } else {
1110
            // override fallback, in case careless user tries to do two
1111
            // opposite things at the same time
1112
            $fallback = true;
1113
        }
1114
1115
        $results = array();
1116
        if ($fallback) {
1117
            // system.multicall is (probably) unsupported by server:
1118
            // emulate multicall via multiple requests
1119
            foreach ($reqs as $req) {
1120
                $results[] = $this->send($req, $timeout, $method);
1121
            }
1122
        } else {
1123
            // user does NOT want to fallback on many single calls:
1124
            // since we should always return an array of responses,
1125
            // return an array with the same error repeated n times
1126
            foreach ($reqs as $req) {
1127
                $results[] = $result;
0 ignored issues
show
Bug introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1128
            }
1129
        }
1130
1131
        return $results;
1132
    }
1133
1134
    /**
1135
     * Attempt to boxcar $reqs via system.multicall.
1136
     *
1137
     * Returns either an array of Response, a single error Response or false (when received response does not respect
1138
     * valid multicall syntax).
1139
     *
1140
     * @param Request[] $reqs
1141
     * @param int $timeout
1142
     * @param string $method
1143
     * @return Response[]|bool|mixed|Response
1144
     */
1145
    private function _try_multicall($reqs, $timeout, $method)
1146
    {
1147
        // Construct multicall request
1148
        $calls = array();
1149
        foreach ($reqs as $req) {
1150
            $call['methodName'] = new Value($req->method(), 'string');
0 ignored issues
show
Coding Style Comprehensibility introduced by
$call was never initialized. Although not strictly required by PHP, it is generally a good practice to add $call = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1151
            $numParams = $req->getNumParams();
1152
            $params = array();
1153
            for ($i = 0; $i < $numParams; $i++) {
1154
                $params[$i] = $req->getParam($i);
1155
            }
1156
            $call['params'] = new Value($params, 'array');
0 ignored issues
show
Documentation introduced by
$params is of type array, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1157
            $calls[] = new Value($call, 'struct');
0 ignored issues
show
Documentation introduced by
$call is of type array<string,object<PhpX...ct<PhpXmlRpc\\Value>"}>, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1158
        }
1159
        $multiCall = new Request('system.multicall');
1160
        $multiCall->addParam(new Value($calls, 'array'));
0 ignored issues
show
Documentation introduced by
$calls is of type array, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1161
1162
        // Attempt RPC call
1163
        $result = $this->send($multiCall, $timeout, $method);
1164
1165
        if ($result->faultCode() != 0) {
1166
            // call to system.multicall failed
1167
            return $result;
1168
        }
1169
1170
        // Unpack responses.
1171
        $rets = $result->value();
1172
1173
        if ($this->return_type == 'xml') {
1174
            return $rets;
1175
        } elseif ($this->return_type == 'phpvals') {
1176
            /// @todo test this code branch...
1177
            $rets = $result->value();
1178
            if (!is_array($rets)) {
1179
                return false;       // bad return type from system.multicall
1180
            }
1181
            $numRets = count($rets);
1182
            if ($numRets != count($reqs)) {
1183
                return false;       // wrong number of return values.
1184
            }
1185
1186
            $response = array();
1187
            for ($i = 0; $i < $numRets; $i++) {
1188
                $val = $rets[$i];
1189
                if (!is_array($val)) {
1190
                    return false;
1191
                }
1192
                switch (count($val)) {
1193
                    case 1:
1194
                        if (!isset($val[0])) {
1195
                            return false;       // Bad value
1196
                        }
1197
                        // Normal return value
1198
                        $response[$i] = new Response($val[0], 0, '', 'phpvals');
1199
                        break;
1200
                    case 2:
1201
                        /// @todo remove usage of @: it is apparently quite slow
1202
                        $code = @$val['faultCode'];
1203
                        if (!is_int($code)) {
1204
                            return false;
1205
                        }
1206
                        $str = @$val['faultString'];
1207
                        if (!is_string($str)) {
1208
                            return false;
1209
                        }
1210
                        $response[$i] = new Response(0, $code, $str);
1211
                        break;
1212
                    default:
1213
                        return false;
1214
                }
1215
            }
1216
1217
            return $response;
1218
        } else {
1219
            // return type == 'xmlrpcvals'
1220
1221
            $rets = $result->value();
1222
            if ($rets->kindOf() != 'array') {
1223
                return false;       // bad return type from system.multicall
1224
            }
1225
            $numRets = $rets->count();
1226
            if ($numRets != count($reqs)) {
1227
                return false;       // wrong number of return values.
1228
            }
1229
1230
            $response = array();
1231
            foreach($rets as $val) {
1232
                switch ($val->kindOf()) {
1233
                    case 'array':
1234
                        if ($val->count() != 1) {
1235
                            return false;       // Bad value
1236
                        }
1237
                        // Normal return value
1238
                        $response[] = new Response($val[0]);
1239
                        break;
1240
                    case 'struct':
1241
                        $code = $val['faultCode'];
1242
                        if ($code->kindOf() != 'scalar' || $code->scalartyp() != 'int') {
1243
                            return false;
1244
                        }
1245
                        $str = $val['faultString'];
1246
                        if ($str->kindOf() != 'scalar' || $str->scalartyp() != 'string') {
1247
                            return false;
1248
                        }
1249
                        $response[] = new Response(0, $code->scalarval(), $str->scalarval());
1250
                        break;
1251
                    default:
1252
                        return false;
1253
                }
1254
            }
1255
1256
            return $response;
1257
        }
1258
    }
1259
}
1260