Completed
Push — master ( 9075d4...7c04cb )
by Gaetano
04:01
created

Client::setUseCurl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace PhpXmlRpc;
4
5
use PhpXmlRpc\Helper\Logger;
6
7
/**
8
 * Used to represent a client of an XML-RPC server.
9
 */
10
class Client
11
{
12
    const USE_CURL_NEVER = 0;
13
    const USE_CURL_ALWAYS = 1;
14
    const USE_CURL_AUTO = 2;
15
16
    /// @todo: do these need to be public?
17
    public $method = 'http';
18
    public $server;
19
    public $port = 0;
20
    public $path;
21
22
    public $errno;
23
    public $errstr;
24
    public $debug = 0;
25
26
    public $username = '';
27
    public $password = '';
28
    public $authtype = 1;
29
30
    public $cert = '';
31
    public $certpass = '';
32
    public $cacert = '';
33
    public $cacertdir = '';
34
    public $key = '';
35
    public $keypass = '';
36
    public $verifypeer = true;
37
    public $verifyhost = 2;
38
    public $sslversion = 0; // corresponds to CURL_SSLVERSION_DEFAULT
39
40
    public $proxy = '';
41
    public $proxyport = 0;
42
    public $proxy_user = '';
43
    public $proxy_pass = '';
44
    public $proxy_authtype = 1;
45
46
    public $cookies = array();
47
    public $extracurlopts = array();
48
    public $use_curl = self::USE_CURL_AUTO;
49
50
    /**
51
     * @var bool
52
     *
53
     * This determines whether the multicall() method will try to take advantage of the system.multicall xmlrpc method
54
     * to dispatch to the server an array of requests in a single http roundtrip or simply execute many consecutive http
55
     * calls. Defaults to FALSE, but it will be enabled automatically on the first failure of execution of
56
     * system.multicall.
57
     */
58
    public $no_multicall = false;
59
60
    /**
61
     * List of http compression methods accepted by the client for responses.
62
     * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib.
63
     *
64
     * NNB: you can set it to any non-empty array for HTTP11 and HTTPS, since
65
     * in those cases it will be up to CURL to decide the compression methods
66
     * it supports. You might check for the presence of 'zlib' in the output of
67
     * curl_version() to determine wheter compression is supported or not
68
     */
69
    public $accepted_compression = array();
70
71
    /**
72
     * Name of compression scheme to be used for sending requests.
73
     * Either null, gzip or deflate.
74
     */
75
76
    public $request_compression = '';
77
78
    /**
79
     * CURL handle: used for keep-alive connections (PHP 4.3.8 up, see:
80
     * http://curl.haxx.se/docs/faq.html#7.3).
81
     */
82
    public $xmlrpc_curl_handle = null;
83
84
    /// Whether to use persistent connections for http 1.1 and https
85
    public $keepalive = false;
86
87
    /// Charset encodings that can be decoded without problems by the client
88
    public $accepted_charset_encodings = array();
89
90
    /**
91
     * The charset encoding that will be used for serializing request sent by the client.
92
     * It defaults to NULL, which means using US-ASCII and encoding all characters outside of the ASCII range using
93
     * their xml character entity representation (this has the benefit that line end characters will not be mangled in
94
     * the transfer, a CR-LF will be preserved as well as a singe LF).
95
     *  Valid values are 'US-ASCII', 'UTF-8' and 'ISO-8859-1'
96
     */
97
    public $request_charset_encoding = '';
98
99
    /**
100
     * Decides the content of Response objects returned by calls to send() and multicall().
101
     * Valid values are 'xmlrpcvals', 'phpvals' or 'xml'.
102
     *
103
     * Determines whether the value returned inside an Response object as results of calls to the send() and multicall()
104
     * methods will be a Value object, a plain php value or a raw xml string.
105
     * Allowed values are 'xmlrpcvals' (the default), 'phpvals' and 'xml'.
106
     * To allow the user to differentiate between a correct and a faulty response, fault responses will be returned as
107
     * Response objects in any case.
108
     * Note that the 'phpvals' setting will yield faster execution times, but some of the information from the original
109
     * response will be lost. It will be e.g. impossible to tell whether a particular php string value was sent by the
110
     * server as an xmlrpc string or base64 value.
111
     */
112
    public $return_type = 'xmlrpcvals';
113
114
    /**
115
     * Sent to servers in http headers.
116
     */
117
    public $user_agent;
118
119
    /**
120
     * @param string $path either the PATH part of the xmlrpc server URL, or complete server URL (in which case you
121
     *                     should use and empty string for all other parameters)
122
     *                     e.g. /xmlrpc/server.php
123
     *                     e.g. http://phpxmlrpc.sourceforge.net/server.php
124
     *                     e.g. https://james:[email protected]:443/xmlrpcserver?agent=007
125
     * @param string $server the server name / ip address
126
     * @param integer $port the port the server is listening on, when omitted defaults to 80 or 443 depending on
127
     *                      protocol used
128
     * @param string $method the http protocol variant: defaults to 'http'; 'https' and 'http11' can be used if CURL is
129
     *                       installed. The value set here can be overridden in any call to $this->send().
130
     */
131
    public function __construct($path, $server = '', $port = '', $method = '')
132
    {
133
        // allow user to specify all params in $path
134
        if ($server == '' && $port == '' && $method == '') {
135
            $parts = parse_url($path);
136
            $server = $parts['host'];
137
            $path = isset($parts['path']) ? $parts['path'] : '';
138
            if (isset($parts['query'])) {
139
                $path .= '?' . $parts['query'];
140
            }
141
            if (isset($parts['fragment'])) {
142
                $path .= '#' . $parts['fragment'];
143
            }
144
            if (isset($parts['port'])) {
145
                $port = $parts['port'];
146
            }
147
            if (isset($parts['scheme'])) {
148
                $method = $parts['scheme'];
149
            }
150
            if (isset($parts['user'])) {
151
                $this->username = $parts['user'];
152
            }
153
            if (isset($parts['pass'])) {
154
                $this->password = $parts['pass'];
155
            }
156
        }
157
        if ($path == '' || $path[0] != '/') {
158
            $this->path = '/' . $path;
159
        } else {
160
            $this->path = $path;
161
        }
162
        $this->server = $server;
163
        if ($port != '') {
164
            $this->port = $port;
165
        }
166
        if ($method != '') {
167
            $this->method = $method;
168
        }
169
170
        // if ZLIB is enabled, let the client by default accept compressed responses
171
        if (function_exists('gzinflate') || (
172
                function_exists('curl_init') && (($info = curl_version()) &&
173
                    ((is_string($info) && strpos($info, 'zlib') !== null) || isset($info['libz_version'])))
174
            )
175
        ) {
176
            $this->accepted_compression = array('gzip', 'deflate');
177
        }
178
179
        // keepalives: enabled by default
180
        $this->keepalive = true;
181
182
        // by default the xml parser can support these 3 charset encodings
183
        $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII');
184
185
        // Add all charsets which mbstring can handle, but remove junk not found in IANA registry at
186
        // http://www.iana.org/assignments/character-sets/character-sets.xhtml
187
        // NB: this is disabled to avoid making all the requests sent huge... mbstring supports more than 80 charsets!
188
        /*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...
189
190
            $encodings = array_diff(mb_list_encodings(), array('pass', 'auto', 'wchar', 'BASE64', 'UUENCODE', 'ASCII',
191
                'HTML-ENTITIES', 'Quoted-Printable', '7bit','8bit', 'byte2be', 'byte2le', 'byte4be', 'byte4le'));
192
            $this->accepted_charset_encodings = array_unique(array_merge($this->accepted_charset_encodings, $encodings));
193
        }*/
194
195
        // initialize user_agent string
196
        $this->user_agent = PhpXmlRpc::$xmlrpcName . ' ' . PhpXmlRpc::$xmlrpcVersion;
197
    }
198
199
    /**
200
     * Enable/disable the echoing to screen of the xmlrpc responses received. The default is not no output anything.
201
     *
202
     * The debugging information at level 1 includes the raw data returned from the XML-RPC server it was querying
203
     * (including bot HTTP headers and the full XML payload), and the PHP value the client attempts to create to
204
     * represent the value returned by the server
205
     * At level2, the complete payload of the xmlrpc request is also printed, before being sent t the server.
206
     *
207
     * This option can be very useful when debugging servers as it allows you to see exactly what the client sends and
208
     * the server returns.
209
     *
210
     * @param integer $level values 0, 1 and 2 are supported (2 = echo sent msg too, before received response)
211
     */
212
    public function setDebug($level)
213
    {
214
        $this->debug = $level;
215
    }
216
217
    /**
218
     * Sets the username and password for authorizing the client to the server.
219
     *
220
     * With the default (HTTP) transport, this information is used for HTTP Basic authorization.
221
     * Note that username and password can also be set using the class constructor.
222
     * With HTTP 1.1 and HTTPS transport, NTLM and Digest authentication protocols are also supported. To enable them use
223
     * the constants CURLAUTH_DIGEST and CURLAUTH_NTLM as values for the auth type parameter.
224
     *
225
     * @param string $user username
226
     * @param string $password password
227
     * @param integer $authType auth type. See curl_setopt man page for supported auth types. Defaults to CURLAUTH_BASIC
228
     *                          (basic auth). Note that auth types NTLM and Digest will only work if the Curl php
229
     *                          extension is enabled.
230
     */
231
    public function setCredentials($user, $password, $authType = 1)
232
    {
233
        $this->username = $user;
234
        $this->password = $password;
235
        $this->authtype = $authType;
236
    }
237
238
    /**
239
     * Set the optional certificate and passphrase used in SSL-enabled communication with a remote server.
240
     *
241
     * Note: to retrieve information about the client certificate on the server side, you will need to look into the
242
     * environment variables which are set up by the webserver. Different webservers will typically set up different
243
     * variables.
244
     *
245
     * @param string $cert the name of a file containing a PEM formatted certificate
246
     * @param string $certPass the password required to use it
247
     */
248
    public function setCertificate($cert, $certPass = '')
249
    {
250
        $this->cert = $cert;
251
        $this->certpass = $certPass;
252
    }
253
254
    /**
255
     * Add a CA certificate to verify server with in SSL-enabled communication when SetSSLVerifypeer has been set to TRUE.
256
     *
257
     * See the php manual page about CURLOPT_CAINFO for more details.
258
     *
259
     * @param string $caCert certificate file name (or dir holding certificates)
260
     * @param bool $isDir set to true to indicate cacert is a dir. defaults to false
261
     */
262
    public function setCaCertificate($caCert, $isDir = false)
263
    {
264
        if ($isDir) {
265
            $this->cacertdir = $caCert;
266
        } else {
267
            $this->cacert = $caCert;
268
        }
269
    }
270
271
    /**
272
     * Set attributes for SSL communication: private SSL key.
273
     *
274
     * NB: does not work in older php/curl installs.
275
     * Thanks to Daniel Convissor.
276
     *
277
     * @param string $key The name of a file containing a private SSL key
278
     * @param string $keyPass The secret password needed to use the private SSL key
279
     */
280
    public function setKey($key, $keyPass)
281
    {
282
        $this->key = $key;
283
        $this->keypass = $keyPass;
284
    }
285
286
    /**
287
     * Set attributes for SSL communication: verify the remote host's SSL certificate, and cause the connection to fail
288
     * if the cert verification fails.
289
     *
290
     * By default, verification is enabled.
291
     * To specify custom SSL certificates to validate the server with, use the setCaCertificate method.
292
     *
293
     * @param bool $i enable/disable verification of peer certificate
294
     */
295
    public function setSSLVerifyPeer($i)
296
    {
297
        $this->verifypeer = $i;
298
    }
299
300
    /**
301
     * Set attributes for SSL communication: verify the remote host's SSL certificate's common name (CN).
302
     *
303
     * Note that support for value 1 has been removed in cURL 7.28.1
304
     *
305
     * @param int $i Set to 1 to only the existence of a CN, not that it matches
306
     */
307
    public function setSSLVerifyHost($i)
308
    {
309
        $this->verifyhost = $i;
310
    }
311
312
    /**
313
     * Set attributes for SSL communication: SSL version to use. Best left at 0 (default value ): let cURL decide
314
     *
315
     * @param int $i
316
     */
317
    public function setSSLVersion($i)
318
    {
319
        $this->sslversion = $i;
320
    }
321
322
    /**
323
     * Set proxy info.
324
     *
325
     * NB: CURL versions before 7.11.10 cannot use a proxy to communicate with https servers.
326
     *
327
     * @param string $proxyHost
328
     * @param string $proxyPort Defaults to 8080 for HTTP and 443 for HTTPS
329
     * @param string $proxyUsername Leave blank if proxy has public access
330
     * @param string $proxyPassword Leave blank if proxy has public access
331
     * @param int $proxyAuthType defaults to CURLAUTH_BASIC (Basic authentication protocol); set to constant CURLAUTH_NTLM
332
     *                           to use NTLM auth with proxy (has effect only when the client uses the HTTP 1.1 protocol)
333
     */
334
    public function setProxy($proxyHost, $proxyPort, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1)
335
    {
336
        $this->proxy = $proxyHost;
337
        $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...
338
        $this->proxy_user = $proxyUsername;
339
        $this->proxy_pass = $proxyPassword;
340
        $this->proxy_authtype = $proxyAuthType;
341
    }
342
343
    /**
344
     * Enables/disables reception of compressed xmlrpc responses.
345
     *
346
     * This requires the "zlib" extension to be enabled in your php install. If it is, by default xmlrpc_client
347
     * instances will enable reception of compressed content.
348
     * Note that enabling reception of compressed responses merely adds some standard http headers to xmlrpc requests.
349
     * It is up to the xmlrpc server to return compressed responses when receiving such requests.
350
     *
351
     * @param string $compMethod either 'gzip', 'deflate', 'any' or ''
352
     */
353
    public function setAcceptedCompression($compMethod)
354
    {
355
        if ($compMethod == 'any') {
356
            $this->accepted_compression = array('gzip', 'deflate');
357
        } 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...
358
            $this->accepted_compression = array();
359
        } else {
360
            $this->accepted_compression = array($compMethod);
361
        }
362
    }
363
364
    /**
365
     * Enables/disables http compression of xmlrpc request.
366
     *
367
     * This requires the "zlib" extension to be enabled in your php install.
368
     * Take care when sending compressed requests: servers might not support them (and automatic fallback to
369
     * uncompressed requests is not yet implemented).
370
     *
371
     * @param string $compMethod either 'gzip', 'deflate' or ''
372
     */
373
    public function setRequestCompression($compMethod)
374
    {
375
        $this->request_compression = $compMethod;
376
    }
377
378
    /**
379
     * Adds a cookie to list of cookies that will be sent to server with every further request (useful e.g. for keeping
380
     * session info outside of the xml-rpc payload).
381
     *
382
     * NB: By default cookies are sent using the 'original/netscape' format, which is also the same as the RFC 2965;
383
     * setting any param but name and value will turn the cookie into a 'version 1' cookie (i.e. RFC 2109 cookie) that
384
     * might not be fully supported by the server. Note that RFC 2109 has currently 'historic' status...
385
     *
386
     * @param string $name nb: will not be escaped in the request's http headers. Take care not to use CTL chars or
387
     *                     separators!
388
     * @param string $value
389
     * @param string $path leave this empty unless the xml-rpc server only accepts RFC 2109 cookies
390
     * @param string $domain leave this empty unless the xml-rpc server only accepts RFC 2109 cookies
391
     * @param int $port leave this empty unless the xml-rpc server only accepts RFC 2109 cookies
392
     *
393
     * @todo check correctness of urlencoding cookie value (copied from php way of doing it, but php is generally sending
394
     *       response not requests. We do the opposite...)
395
     * @todo strip invalid chars from cookie name? As per RFC6265, we should follow RFC2616, Section 2.2
396
     */
397
    public function setCookie($name, $value = '', $path = '', $domain = '', $port = null)
398
    {
399
        $this->cookies[$name]['value'] = urlencode($value);
400
        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...
401
            $this->cookies[$name]['path'] = $path;
402
            $this->cookies[$name]['domain'] = $domain;
403
            $this->cookies[$name]['port'] = $port;
404
            $this->cookies[$name]['version'] = 1;
405
        } else {
406
            $this->cookies[$name]['version'] = 0;
407
        }
408
    }
409
410
    /**
411
     * Directly set cURL options, for extra flexibility (when in cURL mode).
412
     *
413
     * It allows eg. to bind client to a specific IP interface / address.
414
     *
415
     * @param array $options
416
     */
417
    public function setCurlOptions($options)
418
    {
419
        $this->extracurlopts = $options;
420
    }
421
422
    /**
423
     * @param int $useCurlMode self::USE_CURL_ALWAYS, self::USE_CURL_AUTO or self::USE_CURL_NEVER
424
     */
425
    public function setUseCurl($useCurlMode)
426
    {
427
        $this->use_curl = $useCurlMode;
428
    }
429
430
431
    /**
432
     * Set user-agent string that will be used by this client instance in http headers sent to the server.
433
     *
434
     * The default user agent string includes the name of this library and the version number.
435
     *
436
     * @param string $agentString
437
     */
438
    public function setUserAgent($agentString)
439
    {
440
        $this->user_agent = $agentString;
441
    }
442
443
    /**
444
     * Send an xmlrpc request to the server.
445
     *
446
     * @param Request|Request[]|string $req The Request object, or an array of requests for using multicall, or the
447
     *                                      complete xml representation of a request.
448
     *                                      When sending an array of Request objects, the client will try to make use of
449
     *                                      a single 'system.multicall' xml-rpc method call to forward to the server all
450
     *                                      the requests in a single HTTP round trip, unless $this->no_multicall has
451
     *                                      been previously set to TRUE (see the multicall method below), in which case
452
     *                                      many consecutive xmlrpc requests will be sent. The method will return an
453
     *                                      array of Response objects in both cases.
454
     *                                      The third variant allows to build by hand (or any other means) a complete
455
     *                                      xmlrpc request message, and send it to the server. $req should be a string
456
     *                                      containing the complete xml representation of the request. It is e.g. useful
457
     *                                      when, for maximal speed of execution, the request is serialized into a
458
     *                                      string using the native php xmlrpc functions (see http://www.php.net/xmlrpc)
459
     * @param integer $timeout Connection timeout, in seconds, If unspecified, a platform specific timeout will apply.
460
     *                         This timeout value is passed to fsockopen(). It is also used for detecting server
461
     *                         timeouts during communication (i.e. if the server does not send anything to the client
462
     *                         for $timeout seconds, the connection will be closed).
463
     * @param string $method valid values are 'http', 'http11' and 'https'. If left unspecified, the http protocol
464
     *                       chosen during creation of the object will be used.
465
     *
466
     * @return Response|Response[] Note that the client will always return a Response object, even if the call fails
467
     */
468
    public function send($req, $timeout = 0, $method = '')
469
    {
470
        // if user does not specify http protocol, use native method of this client
471
        // (i.e. method set during call to constructor)
472
        if ($method == '') {
473
            $method = $this->method;
474
        }
475
476
        if (is_array($req)) {
477
            // $req is an array of Requests
478
            $r = $this->multicall($req, $timeout, $method);
479
480
            return $r;
481
        } elseif (is_string($req)) {
482
            $n = new Request('');
483
            $n->payload = $req;
484
            $req = $n;
485
        }
486
487
        // where req is a Request
488
        $req->setDebug($this->debug);
489
490
        /// @todo we could be smarter about this and force usage of curl in scenarios where it is both available and
491
        ///       needed, such as digest or ntlm auth. Do not attempt to use it for https if not present
492
        $useCurl = ($this->use_curl == self::USE_CURL_ALWAYS) || ($this->use_curl == self::USE_CURL_AUTO &&
493
            ($method == 'https' || $method == 'http11'));
494
495
        if ($useCurl) {
496
            $r = $this->sendPayloadCURL(
497
                $req,
498
                $this->server,
499
                $this->port,
500
                $timeout,
501
                $this->username,
502
                $this->password,
503
                $this->authtype,
504
                $this->cert,
505
                $this->certpass,
506
                $this->cacert,
507
                $this->cacertdir,
508
                $this->proxy,
509
                $this->proxyport,
510
                $this->proxy_user,
511
                $this->proxy_pass,
512
                $this->proxy_authtype,
513
                // bc
514
                $method == 'http11' ? 'http' : $method,
515
                $this->keepalive,
516
                $this->key,
517
                $this->keypass,
518
                $this->sslversion
519
            );
520
        } else {
521
            // plain 'http 1.0': default to using socket
522
            $r = $this->sendPayloadSocket(
523
                $req,
524
                $this->server,
525
                $this->port,
526
                $timeout,
527
                $this->username,
528
                $this->password,
529
                $this->authtype,
530
                $this->cert,
531
                $this->certpass,
532
                $this->cacert,
533
                $this->cacertdir,
534
                $this->proxy,
535
                $this->proxyport,
536
                $this->proxy_user,
537
                $this->proxy_pass,
538
                $this->proxy_authtype,
539
                $method,
540
                $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...
541
                $this->key,
542
                $this->keypass,
543
                $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...
544
            );
545
        }
546
547
        return $r;
548
    }
549
550
    /**
551
     * @deprecated
552
     * @param Request $req
553
     * @param string $server
554
     * @param int $port
555
     * @param int $timeout
556
     * @param string $username
557
     * @param string $password
558
     * @param int $authType
559
     * @param string $proxyHost
560
     * @param int $proxyPort
561
     * @param string $proxyUsername
562
     * @param string $proxyPassword
563
     * @param int $proxyAuthType
564
     * @param string $method
565
     * @return Response
566
     */
567
    protected function sendPayloadHTTP10($req, $server, $port, $timeout = 0, $username = '', $password = '',
568
        $authType = 1, $proxyHost = '', $proxyPort = 0, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1,
569
        $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...
570
    {
571
        return $this->sendPayloadSocket($req, $server, $port, $timeout, $username, $password, $authType, null, null,
572
            null, null, $proxyHost, $proxyPort, $proxyUsername, $proxyPassword, $proxyAuthType);
573
    }
574
575
    /**
576
     * @deprecated
577
     * @param Request $req
578
     * @param string $server
579
     * @param int $port
580
     * @param int $timeout
581
     * @param string $username
582
     * @param string $password
583
     * @param int $authType
584
     * @param string $cert
585
     * @param string $certPass
586
     * @param string $caCert
587
     * @param string $caCertDir
588
     * @param string $proxyHost
589
     * @param int $proxyPort
590
     * @param string $proxyUsername
591
     * @param string $proxyPassword
592
     * @param int $proxyAuthType
593
     * @param bool $keepAlive
594
     * @param string $key
595
     * @param string $keyPass
596
     * @param int $sslVersion
597
     * @return Response
598
     */
599
    protected function sendPayloadHTTPS($req, $server, $port, $timeout = 0, $username = '',  $password = '',
600
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
601
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $keepAlive = false, $key = '', $keyPass = '',
602
        $sslVersion = 0)
603
    {
604
        return $this->sendPayloadCURL($req, $server, $port, $timeout, $username,
605
            $password, $authType, $cert, $certPass, $caCert, $caCertDir, $proxyHost, $proxyPort,
606
            $proxyUsername, $proxyPassword, $proxyAuthType, 'https', $keepAlive, $key, $keyPass, $sslVersion);
607
    }
608
609
    /**
610
     * @param Request $req
611
     * @param string $server
612
     * @param int $port
613
     * @param int $timeout
614
     * @param string $username
615
     * @param string $password
616
     * @param int $authType only value supported is 1
617
     * @param string $cert
618
     * @param string $certPass
619
     * @param string $caCert
620
     * @param string $caCertDir
621
     * @param string $proxyHost
622
     * @param int $proxyPort
623
     * @param string $proxyUsername
624
     * @param string $proxyPassword
625
     * @param int $proxyAuthType only value supported is 1
626
     * @param string $method 'http' (synonym for 'http10'), 'http10' or 'https'
627
     * @param string $key
628
     * @param string $keyPass @todo not implemented yet.
629
     * @param int $sslVersion @todo not implemented yet. See http://php.net/manual/en/migration56.openssl.php
630
     * @return Response
631
     */
632
    protected function sendPayloadSocket($req, $server, $port, $timeout = 0, $username = '', $password = '',
633
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
634
        $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...
635
        $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...
636
    {
637
        if ($port == 0) {
638
            $port = ( $method === 'https' ) ? 443 : 80;
639
        }
640
641
        // Only create the payload if it was not created previously
642
        if (empty($req->payload)) {
643
            $req->createPayload($this->request_charset_encoding);
644
        }
645
646
        $payload = $req->payload;
647
        // Deflate request body and set appropriate request headers
648 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...
649
            if ($this->request_compression == 'gzip') {
650
                $a = @gzencode($payload);
651
                if ($a) {
652
                    $payload = $a;
653
                    $encodingHdr = "Content-Encoding: gzip\r\n";
654
                }
655
            } else {
656
                $a = @gzcompress($payload);
657
                if ($a) {
658
                    $payload = $a;
659
                    $encodingHdr = "Content-Encoding: deflate\r\n";
660
                }
661
            }
662
        } else {
663
            $encodingHdr = '';
664
        }
665
666
        // thanks to Grant Rauscher <[email protected]> for this
667
        $credentials = '';
668
        if ($username != '') {
669
            $credentials = 'Authorization: Basic ' . base64_encode($username . ':' . $password) . "\r\n";
670
            if ($authType != 1) {
671
                error_log('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported with HTTP 1.0');
672
            }
673
        }
674
675
        $acceptedEncoding = '';
676
        if (is_array($this->accepted_compression) && count($this->accepted_compression)) {
677
            $acceptedEncoding = 'Accept-Encoding: ' . implode(', ', $this->accepted_compression) . "\r\n";
678
        }
679
680
        $proxyCredentials = '';
681
        if ($proxyHost) {
682
            if ($proxyPort == 0) {
683
                $proxyPort = 8080;
684
            }
685
            $connectServer = $proxyHost;
686
            $connectPort = $proxyPort;
687
            $transport = 'tcp';
688
            $uri = 'http://' . $server . ':' . $port . $this->path;
689
            if ($proxyUsername != '') {
690
                if ($proxyAuthType != 1) {
691
                    error_log('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported with HTTP 1.0');
692
                }
693
                $proxyCredentials = 'Proxy-Authorization: Basic ' . base64_encode($proxyUsername . ':' . $proxyPassword) . "\r\n";
694
            }
695
        } else {
696
            $connectServer = $server;
697
            $connectPort = $port;
698
            /// @todo if supporting https, we should support all its current options as well: peer name verification etc...
699
            $transport = ( $method === 'https' ) ? 'tls' : 'tcp';
700
            $uri = $this->path;
701
        }
702
703
        // Cookie generation, as per rfc2965 (version 1 cookies) or
704
        // netscape's rules (version 0 cookies)
705
        $cookieHeader = '';
706
        if (count($this->cookies)) {
707
            $version = '';
708
            foreach ($this->cookies as $name => $cookie) {
709
                if ($cookie['version']) {
710
                    $version = ' $Version="' . $cookie['version'] . '";';
711
                    $cookieHeader .= ' ' . $name . '="' . $cookie['value'] . '";';
712
                    if ($cookie['path']) {
713
                        $cookieHeader .= ' $Path="' . $cookie['path'] . '";';
714
                    }
715
                    if ($cookie['domain']) {
716
                        $cookieHeader .= ' $Domain="' . $cookie['domain'] . '";';
717
                    }
718
                    if ($cookie['port']) {
719
                        $cookieHeader .= ' $Port="' . $cookie['port'] . '";';
720
                    }
721
                } else {
722
                    $cookieHeader .= ' ' . $name . '=' . $cookie['value'] . ";";
723
                }
724
            }
725
            $cookieHeader = 'Cookie:' . $version . substr($cookieHeader, 0, -1) . "\r\n";
726
        }
727
728
        // omit port if default
729
        if (($port == 80 && in_array($method, array('http', 'http10'))) || ($port == 443 && $method == 'https')) {
730
            $port =  '';
731
        } else {
732
            $port = ':' . $port;
733
        }
734
735
        $op = 'POST ' . $uri . " HTTP/1.0\r\n" .
736
            'User-Agent: ' . $this->user_agent . "\r\n" .
737
            'Host: ' . $server . $port . "\r\n" .
738
            $credentials .
739
            $proxyCredentials .
740
            $acceptedEncoding .
741
            $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...
742
            'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings) . "\r\n" .
743
            $cookieHeader .
744
            'Content-Type: ' . $req->content_type . "\r\nContent-Length: " .
745
            strlen($payload) . "\r\n\r\n" .
746
            $payload;
747
748
        if ($this->debug > 1) {
749
            Logger::instance()->debugMessage("---SENDING---\n$op\n---END---");
750
        }
751
752
        $contextOptions = array();
753
        if ($method == 'https') {
754
            if ($cert != '') {
755
                $contextOptions['ssl']['local_cert'] = $cert;
756
                if ($certPass != '') {
757
                    $contextOptions['ssl']['passphrase'] = $certPass;
758
                }
759
            }
760
            if ($caCert != '') {
761
                $contextOptions['ssl']['cafile'] = $caCert;
762
            }
763
            if ($caCertDir != '') {
764
                $contextOptions['ssl']['capath'] = $caCertDir;
765
            }
766
            if ($key != '') {
767
                $contextOptions['ssl']['local_pk'] = $key;
768
            }
769
            $contextOptions['ssl']['verify_peer'] = $this->verifypeer;
770
771
        }
772
        $context = stream_context_create($contextOptions);
773
774
        if ($timeout <= 0) {
775
            $connectTimeout = ini_get('default_socket_timeout');
776
        } else {
777
            $connectTimeout = $timeout;
778
        }
779
780
        $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr, $connectTimeout,
781
            STREAM_CLIENT_CONNECT, $context);
782
        if ($fp) {
783
            if ($timeout > 0) {
784
                stream_set_timeout($fp, $timeout);
785
            }
786
        } else {
787
            $this->errstr = 'Connect error: ' . $this->errstr;
788
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr . ' (' . $this->errno . ')');
789
790
            return $r;
791
        }
792
793
        if (!fputs($fp, $op, strlen($op))) {
794
            fclose($fp);
795
            $this->errstr = 'Write error';
796
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr);
797
798
            return $r;
799
        } else {
800
            // reset errno and errstr on successful socket connection
801
            $this->errstr = '';
802
        }
803
        // G. Giunta 2005/10/24: close socket before parsing.
804
        // should yield slightly better execution times, and make easier recursive calls (e.g. to follow http redirects)
805
        $ipd = '';
806
        do {
807
            // shall we check for $data === FALSE?
808
            // as per the manual, it signals an error
809
            $ipd .= fread($fp, 32768);
810
        } while (!feof($fp));
811
        fclose($fp);
812
        $r = $req->parseResponse($ipd, false, $this->return_type);
813
814
        return $r;
815
    }
816
817
    /**
818
     * Contributed by Justin Miller <[email protected]>
819
     * Requires curl to be built into PHP
820
     * NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers!
821
     *
822
     * @param Request $req
823
     * @param string $server
824
     * @param int $port
825
     * @param int $timeout
826
     * @param string $username
827
     * @param string $password
828
     * @param int $authType
829
     * @param string $cert
830
     * @param string $certPass
831
     * @param string $caCert
832
     * @param string $caCertDir
833
     * @param string $proxyHost
834
     * @param int $proxyPort
835
     * @param string $proxyUsername
836
     * @param string $proxyPassword
837
     * @param int $proxyAuthType
838
     * @param string $method 'http' (let curl decide), 'http10', 'http11' or 'https'
839
     * @param bool $keepAlive
840
     * @param string $key
841
     * @param string $keyPass
842
     * @param int $sslVersion
843
     * @return Response
844
     */
845
    protected function sendPayloadCURL($req, $server, $port, $timeout = 0, $username = '', $password = '',
846
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
847
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'https', $keepAlive = false, $key = '',
848
        $keyPass = '', $sslVersion = 0)
849
    {
850
        if (!function_exists('curl_init')) {
851
            $this->errstr = 'CURL unavailable on this install';
852
            return new Response(0, PhpXmlRpc::$xmlrpcerr['no_curl'], PhpXmlRpc::$xmlrpcstr['no_curl']);
853
        }
854
        if ($method == 'https') {
855
            if (($info = curl_version()) &&
856
                ((is_string($info) && strpos($info, 'OpenSSL') === null) || (is_array($info) && !isset($info['ssl_version'])))
857
            ) {
858
                $this->errstr = 'SSL unavailable on this install';
859
                return new Response(0, PhpXmlRpc::$xmlrpcerr['no_ssl'], PhpXmlRpc::$xmlrpcstr['no_ssl']);
860
            }
861
        }
862
863
        if ($port == 0) {
864
            if (in_array($method, array('http', 'http10', 'http11'))) {
865
                $port = 80;
866
            } else {
867
                $port = 443;
868
            }
869
        }
870
871
        // Only create the payload if it was not created previously
872
        if (empty($req->payload)) {
873
            $req->createPayload($this->request_charset_encoding);
874
        }
875
876
        // Deflate request body and set appropriate request headers
877
        $payload = $req->payload;
878 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...
879
            if ($this->request_compression == 'gzip') {
880
                $a = @gzencode($payload);
881
                if ($a) {
882
                    $payload = $a;
883
                    $encodingHdr = 'Content-Encoding: gzip';
884
                }
885
            } else {
886
                $a = @gzcompress($payload);
887
                if ($a) {
888
                    $payload = $a;
889
                    $encodingHdr = 'Content-Encoding: deflate';
890
                }
891
            }
892
        } else {
893
            $encodingHdr = '';
894
        }
895
896
        if ($this->debug > 1) {
897
            Logger::instance()->debugMessage("---SENDING---\n$payload\n---END---");
898
        }
899
900
        if (!$keepAlive || !$this->xmlrpc_curl_handle) {
901
            if ($method == 'http11' || $method == 'http10') {
902
                $protocol = 'http';
903
            } else {
904
                $protocol = $method;
905
            }
906
            $curl = curl_init($protocol . '://' . $server . ':' . $port . $this->path);
907
            if ($keepAlive) {
908
                $this->xmlrpc_curl_handle = $curl;
909
            }
910
        } else {
911
            $curl = $this->xmlrpc_curl_handle;
912
        }
913
914
        // results into variable
915
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
916
917
        if ($this->debug > 1) {
918
            curl_setopt($curl, CURLOPT_VERBOSE, true);
919
            /// @todo allow callers to redirect curlopt_stderr to some stream which can be buffered
920
        }
921
        curl_setopt($curl, CURLOPT_USERAGENT, $this->user_agent);
922
        // required for XMLRPC: post the data
923
        curl_setopt($curl, CURLOPT_POST, 1);
924
        // the data
925
        curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
926
927
        // return the header too
928
        curl_setopt($curl, CURLOPT_HEADER, 1);
929
930
        // NB: if we set an empty string, CURL will add http header indicating
931
        // ALL methods it is supporting. This is possibly a better option than
932
        // letting the user tell what curl can / cannot do...
933
        if (is_array($this->accepted_compression) && count($this->accepted_compression)) {
934
            //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...
935
            // empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
936
            if (count($this->accepted_compression) == 1) {
937
                curl_setopt($curl, CURLOPT_ENCODING, $this->accepted_compression[0]);
938
            } else {
939
                curl_setopt($curl, CURLOPT_ENCODING, '');
940
            }
941
        }
942
        // extra headers
943
        $headers = array('Content-Type: ' . $req->content_type, 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings));
944
        // if no keepalive is wanted, let the server know it in advance
945
        if (!$keepAlive) {
946
            $headers[] = 'Connection: close';
947
        }
948
        // request compression header
949
        if ($encodingHdr) {
950
            $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...
951
        }
952
953
        // Fix the HTTP/1.1 417 Expectation Failed Bug (curl by default adds a 'Expect: 100-continue' header when POST
954
        // size exceeds 1025 bytes, apparently)
955
        $headers[] = 'Expect:';
956
957
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
958
        // timeout is borked
959
        if ($timeout) {
960
            curl_setopt($curl, CURLOPT_TIMEOUT, $timeout == 1 ? 1 : $timeout - 1);
961
        }
962
963
        if ($method == 'http10') {
964
            curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
965
        } elseif ($method == 'http11') {
966
            curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
967
        }
968
969 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...
970
            curl_setopt($curl, CURLOPT_USERPWD, $username . ':' . $password);
971
            if (defined('CURLOPT_HTTPAUTH')) {
972
                curl_setopt($curl, CURLOPT_HTTPAUTH, $authType);
973
            } elseif ($authType != 1) {
974
                error_log('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported by the current PHP/curl install');
975
            }
976
        }
977
978
        if ($method == 'https') {
979
            // set cert file
980
            if ($cert) {
981
                curl_setopt($curl, CURLOPT_SSLCERT, $cert);
982
            }
983
            // set cert password
984
            if ($certPass) {
985
                curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $certPass);
986
            }
987
            // whether to verify remote host's cert
988
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verifypeer);
989
            // set ca certificates file/dir
990
            if ($caCert) {
991
                curl_setopt($curl, CURLOPT_CAINFO, $caCert);
992
            }
993
            if ($caCertDir) {
994
                curl_setopt($curl, CURLOPT_CAPATH, $caCertDir);
995
            }
996
            // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
997
            if ($key) {
998
                curl_setopt($curl, CURLOPT_SSLKEY, $key);
999
            }
1000
            // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1001
            if ($keyPass) {
1002
                curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $keyPass);
1003
            }
1004
            // 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
1005
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $this->verifyhost);
1006
            // allow usage of different SSL versions
1007
            curl_setopt($curl, CURLOPT_SSLVERSION, $sslVersion);
1008
        }
1009
1010
        // proxy info
1011
        if ($proxyHost) {
1012
            if ($proxyPort == 0) {
1013
                $proxyPort = 8080; // NB: even for HTTPS, local connection is on port 8080
1014
            }
1015
            curl_setopt($curl, CURLOPT_PROXY, $proxyHost . ':' . $proxyPort);
1016 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...
1017
                curl_setopt($curl, CURLOPT_PROXYUSERPWD, $proxyUsername . ':' . $proxyPassword);
1018
                if (defined('CURLOPT_PROXYAUTH')) {
1019
                    curl_setopt($curl, CURLOPT_PROXYAUTH, $proxyAuthType);
1020
                } elseif ($proxyAuthType != 1) {
1021
                    error_log('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported by the current PHP/curl install');
1022
                }
1023
            }
1024
        }
1025
1026
        // NB: should we build cookie http headers by hand rather than let CURL do it?
1027
        // the following code does not honour 'expires', 'path' and 'domain' cookie attributes
1028
        // set to client obj the the user...
1029
        if (count($this->cookies)) {
1030
            $cookieHeader = '';
1031
            foreach ($this->cookies as $name => $cookie) {
1032
                $cookieHeader .= $name . '=' . $cookie['value'] . '; ';
1033
            }
1034
            curl_setopt($curl, CURLOPT_COOKIE, substr($cookieHeader, 0, -2));
1035
        }
1036
1037
        foreach ($this->extracurlopts as $opt => $val) {
1038
            curl_setopt($curl, $opt, $val);
1039
        }
1040
1041
        $result = curl_exec($curl);
1042
1043
        if ($this->debug > 1) {
1044
            $message = "---CURL INFO---\n";
1045
            foreach (curl_getinfo($curl) as $name => $val) {
1046
                if (is_array($val)) {
1047
                    $val = implode("\n", $val);
1048
                }
1049
                $message .= $name . ': ' . $val . "\n";
1050
            }
1051
            $message .= '---END---';
1052
            Logger::instance()->debugMessage($message);
1053
        }
1054
1055
        if (!$result) {
1056
            /// @todo we should use a better check here - what if we get back '' or '0'?
1057
1058
            $this->errstr = 'no response';
1059
            $resp = new Response(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] . ': ' . curl_error($curl));
1060
            curl_close($curl);
1061
            if ($keepAlive) {
1062
                $this->xmlrpc_curl_handle = null;
1063
            }
1064
        } else {
1065
            if (!$keepAlive) {
1066
                curl_close($curl);
1067
            }
1068
            $resp = $req->parseResponse($result, true, $this->return_type);
1069
            // if we got back a 302, we can not reuse the curl handle for later calls
1070
            if ($resp->faultCode() == PhpXmlRpc::$xmlrpcerr['http_error'] && $keepAlive) {
1071
                curl_close($curl);
1072
                $this->xmlrpc_curl_handle = null;
1073
            }
1074
        }
1075
1076
        return $resp;
1077
    }
1078
1079
    /**
1080
     * Send an array of requests and return an array of responses.
1081
     *
1082
     * Unless $this->no_multicall has been set to true, it will try first to use one single xmlrpc call to server method
1083
     * system.multicall, and revert to sending many successive calls in case of failure.
1084
     * This failure is also stored in $this->no_multicall for subsequent calls.
1085
     * Unfortunately, there is no server error code universally used to denote the fact that multicall is unsupported,
1086
     * so there is no way to reliably distinguish between that and a temporary failure.
1087
     * If you are sure that server supports multicall and do not want to fallback to using many single calls, set the
1088
     * fourth parameter to FALSE.
1089
     *
1090
     * NB: trying to shoehorn extra functionality into existing syntax has resulted
1091
     * in pretty much convoluted code...
1092
     *
1093
     * @param Request[] $reqs an array of Request objects
1094
     * @param integer $timeout connection timeout (in seconds). See the details in the docs for the send() method
1095
     * @param string $method the http protocol variant to be used. See the details in the docs for the send() method
1096
     * @param boolean fallback When true, upon receiving an error during multicall, multiple single calls will be
1097
     *                         attempted
1098
     *
1099
     * @return Response[]
1100
     */
1101
    public function multicall($reqs, $timeout = 0, $method = '', $fallback = true)
1102
    {
1103
        if ($method == '') {
1104
            $method = $this->method;
1105
        }
1106
        if (!$this->no_multicall) {
1107
            $results = $this->_try_multicall($reqs, $timeout, $method);
1108
            if (is_array($results)) {
1109
                // System.multicall succeeded
1110
                return $results;
1111
            } else {
1112
                // either system.multicall is unsupported by server,
1113
                // or call failed for some other reason.
1114 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...
1115
                    // Don't try it next time...
1116
                    $this->no_multicall = true;
1117
                } else {
1118
                    if (is_a($results, '\PhpXmlRpc\Response')) {
1119
                        $result = $results;
1120
                    } else {
1121
                        $result = new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'], PhpXmlRpc::$xmlrpcstr['multicall_error']);
1122
                    }
1123
                }
1124
            }
1125
        } else {
1126
            // override fallback, in case careless user tries to do two
1127
            // opposite things at the same time
1128
            $fallback = true;
1129
        }
1130
1131
        $results = array();
1132
        if ($fallback) {
1133
            // system.multicall is (probably) unsupported by server:
1134
            // emulate multicall via multiple requests
1135
            foreach ($reqs as $req) {
1136
                $results[] = $this->send($req, $timeout, $method);
1137
            }
1138
        } else {
1139
            // user does NOT want to fallback on many single calls:
1140
            // since we should always return an array of responses,
1141
            // return an array with the same error repeated n times
1142
            foreach ($reqs as $req) {
1143
                $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...
1144
            }
1145
        }
1146
1147
        return $results;
1148
    }
1149
1150
    /**
1151
     * Attempt to boxcar $reqs via system.multicall.
1152
     *
1153
     * Returns either an array of Response, a single error Response or false (when received response does not respect
1154
     * valid multicall syntax).
1155
     *
1156
     * @param Request[] $reqs
1157
     * @param int $timeout
1158
     * @param string $method
1159
     * @return Response[]|bool|mixed|Response
1160
     */
1161
    private function _try_multicall($reqs, $timeout, $method)
1162
    {
1163
        // Construct multicall request
1164
        $calls = array();
1165
        foreach ($reqs as $req) {
1166
            $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...
1167
            $numParams = $req->getNumParams();
1168
            $params = array();
1169
            for ($i = 0; $i < $numParams; $i++) {
1170
                $params[$i] = $req->getParam($i);
1171
            }
1172
            $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...
1173
            $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...
1174
        }
1175
        $multiCall = new Request('system.multicall');
1176
        $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...
1177
1178
        // Attempt RPC call
1179
        $result = $this->send($multiCall, $timeout, $method);
1180
1181
        if ($result->faultCode() != 0) {
1182
            // call to system.multicall failed
1183
            return $result;
1184
        }
1185
1186
        // Unpack responses.
1187
        $rets = $result->value();
1188
1189
        if ($this->return_type == 'xml') {
1190
            return $rets;
1191
        } elseif ($this->return_type == 'phpvals') {
1192
            /// @todo test this code branch...
1193
            $rets = $result->value();
1194
            if (!is_array($rets)) {
1195
                return false;       // bad return type from system.multicall
1196
            }
1197
            $numRets = count($rets);
1198
            if ($numRets != count($reqs)) {
1199
                return false;       // wrong number of return values.
1200
            }
1201
1202
            $response = array();
1203
            for ($i = 0; $i < $numRets; $i++) {
1204
                $val = $rets[$i];
1205
                if (!is_array($val)) {
1206
                    return false;
1207
                }
1208
                switch (count($val)) {
1209
                    case 1:
1210
                        if (!isset($val[0])) {
1211
                            return false;       // Bad value
1212
                        }
1213
                        // Normal return value
1214
                        $response[$i] = new Response($val[0], 0, '', 'phpvals');
1215
                        break;
1216
                    case 2:
1217
                        /// @todo remove usage of @: it is apparently quite slow
1218
                        $code = @$val['faultCode'];
1219
                        if (!is_int($code)) {
1220
                            return false;
1221
                        }
1222
                        $str = @$val['faultString'];
1223
                        if (!is_string($str)) {
1224
                            return false;
1225
                        }
1226
                        $response[$i] = new Response(0, $code, $str);
1227
                        break;
1228
                    default:
1229
                        return false;
1230
                }
1231
            }
1232
1233
            return $response;
1234
        } else {
1235
            // return type == 'xmlrpcvals'
1236
1237
            $rets = $result->value();
1238
            if ($rets->kindOf() != 'array') {
1239
                return false;       // bad return type from system.multicall
1240
            }
1241
            $numRets = $rets->count();
1242
            if ($numRets != count($reqs)) {
1243
                return false;       // wrong number of return values.
1244
            }
1245
1246
            $response = array();
1247
            foreach($rets as $val) {
1248
                switch ($val->kindOf()) {
1249
                    case 'array':
1250
                        if ($val->count() != 1) {
1251
                            return false;       // Bad value
1252
                        }
1253
                        // Normal return value
1254
                        $response[] = new Response($val[0]);
1255
                        break;
1256
                    case 'struct':
1257
                        $code = $val['faultCode'];
1258
                        if ($code->kindOf() != 'scalar' || $code->scalartyp() != 'int') {
1259
                            return false;
1260
                        }
1261
                        $str = $val['faultString'];
1262
                        if ($str->kindOf() != 'scalar' || $str->scalartyp() != 'string') {
1263
                            return false;
1264
                        }
1265
                        $response[] = new Response(0, $code->scalarval(), $str->scalarval());
1266
                        break;
1267
                    default:
1268
                        return false;
1269
                }
1270
            }
1271
1272
            return $response;
1273
        }
1274
    }
1275
}
1276