Completed
Push — master ( 247d97...94468e )
by Gaetano
05:26
created

Client::send()   C

Complexity

Conditions 9
Paths 34

Size

Total Lines 80
Code Lines 59

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 63
CRAP Score 9

Importance

Changes 0
Metric Value
cc 9
eloc 59
nc 34
nop 3
dl 0
loc 80
ccs 63
cts 63
cp 1
crap 9
rs 5.6205
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 605
    public function __construct($path, $server = '', $port = '', $method = '')
132
    {
133
        // allow user to specify all params in $path
134 605
        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 605
        if ($path == '' || $path[0] != '/') {
158
            $this->path = '/' . $path;
159
        } else {
160 605
            $this->path = $path;
161
        }
162 605
        $this->server = $server;
163 605
        if ($port != '') {
164 3
            $this->port = $port;
165 3
        }
166 605
        if ($method != '') {
167
            $this->method = $method;
168
        }
169
170
        // if ZLIB is enabled, let the client by default accept compressed responses
171 605
        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 605
        ) {
176 605
            $this->accepted_compression = array('gzip', 'deflate');
177 605
        }
178
179
        // keepalives: enabled by default
180 605
        $this->keepalive = true;
181
182
        // by default the xml parser can support these 3 charset encodings
183 605
        $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 605
        $this->user_agent = PhpXmlRpc::$xmlrpcName . ' ' . PhpXmlRpc::$xmlrpcVersion;
197 605
    }
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 605
    public function setDebug($level)
213
    {
214 605
        $this->debug = $level;
215 605
    }
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 62
    public function setCredentials($user, $password, $authType = 1)
232
    {
233 62
        $this->username = $user;
234 62
        $this->password = $password;
235 62
        $this->authtype = $authType;
236 62
    }
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 93
    public function setSSLVerifyPeer($i)
296
    {
297 93
        $this->verifypeer = $i;
298 93
    }
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 93
    public function setSSLVerifyHost($i)
308
    {
309 93
        $this->verifyhost = $i;
310 93
    }
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 93
    public function setSSLVersion($i)
318
    {
319 93
        $this->sslversion = $i;
320 93
    }
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 93
    public function setProxy($proxyHost, $proxyPort, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1)
335
    {
336 93
        $this->proxy = $proxyHost;
337 93
        $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 93
        $this->proxy_user = $proxyUsername;
339 93
        $this->proxy_pass = $proxyPassword;
340 93
        $this->proxy_authtype = $proxyAuthType;
341 93
    }
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 507
    public function setCookie($name, $value = '', $path = '', $domain = '', $port = null)
398
    {
399 507
        $this->cookies[$name]['value'] = urlencode($value);
400 507
        if ($path || $domain || $port) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $port of type integer|null is loosely compared to true; this is ambiguous if the integer can be 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 507
            $this->cookies[$name]['version'] = 0;
407
        }
408 507
    }
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 62
    public function setUseCurl($useCurlMode)
426
    {
427 62
        $this->use_curl = $useCurlMode;
428 62
    }
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 586
    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 586
        if ($method == '') {
473 79
            $method = $this->method;
474 79
        }
475
476 586
        if (is_array($req)) {
477
            // $req is an array of Requests
478 58
            $r = $this->multicall($req, $timeout, $method);
479
480 58
            return $r;
481 586
        } elseif (is_string($req)) {
482 26
            $n = new Request('');
483 26
            $n->payload = $req;
484 26
            $req = $n;
485 26
        }
486
487
        // where req is a Request
488 586
        $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 586
        $useCurl = ($this->use_curl == self::USE_CURL_ALWAYS) || ($this->use_curl == self::USE_CURL_AUTO &&
493 586
            ($method == 'https' || $method == 'http11'));
494
495 586
        if ($useCurl) {
496 242
            $r = $this->sendPayloadCURL(
497 242
                $req,
498 242
                $this->server,
499 242
                $this->port,
500 242
                $timeout,
501 242
                $this->username,
502 242
                $this->password,
503 242
                $this->authtype,
504 242
                $this->cert,
505 242
                $this->certpass,
506 242
                $this->cacert,
507 242
                $this->cacertdir,
508 242
                $this->proxy,
509 242
                $this->proxyport,
510 242
                $this->proxy_user,
511 242
                $this->proxy_pass,
512 242
                $this->proxy_authtype,
513
                // bc
514 242
                $method == 'http11' ? 'http' : $method,
515 242
                $this->keepalive,
516 242
                $this->key,
517 242
                $this->keypass,
518 242
                $this->sslversion
519 242
            );
520 242
        } else {
521
            // plain 'http 1.0': default to using socket
522 344
            $r = $this->sendPayloadSocket(
523 344
                $req,
524 344
                $this->server,
525 344
                $this->port,
526 344
                $timeout,
527 344
                $this->username,
528 344
                $this->password,
529 344
                $this->authtype,
530 344
                $this->cert,
531 344
                $this->certpass,
532 344
                $this->cacert,
533 344
                $this->cacertdir,
534 344
                $this->proxy,
535 344
                $this->proxyport,
536 344
                $this->proxy_user,
537 344
                $this->proxy_pass,
538 344
                $this->proxy_authtype,
539 344
                $method,
540 344
                $this->key,
541 344
                $this->keypass,
542 344
                $this->sslversion
543 344
            );
544
        }
545
546 586
        return $r;
547
    }
548
549
    /**
550
     * @deprecated
551
     * @param Request $req
552
     * @param string $server
553
     * @param int $port
554
     * @param int $timeout
555
     * @param string $username
556
     * @param string $password
557
     * @param int $authType
558
     * @param string $proxyHost
559
     * @param int $proxyPort
560
     * @param string $proxyUsername
561
     * @param string $proxyPassword
562
     * @param int $proxyAuthType
563
     * @param string $method
564
     * @return Response
565
     */
566
    protected function sendPayloadHTTP10($req, $server, $port, $timeout = 0, $username = '', $password = '',
567
        $authType = 1, $proxyHost = '', $proxyPort = 0, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1,
568
        $method='http')
569
    {
570
        return $this->sendPayloadSocket($req, $server, $port, $timeout, $username, $password, $authType, null, null,
571
            null, null, $proxyHost, $proxyPort, $proxyUsername, $proxyPassword, $proxyAuthType, $method);
572
    }
573
574
    /**
575
     * @deprecated
576
     * @param Request $req
577
     * @param string $server
578
     * @param int $port
579
     * @param int $timeout
580
     * @param string $username
581
     * @param string $password
582
     * @param int $authType
583
     * @param string $cert
584
     * @param string $certPass
585
     * @param string $caCert
586
     * @param string $caCertDir
587
     * @param string $proxyHost
588
     * @param int $proxyPort
589
     * @param string $proxyUsername
590
     * @param string $proxyPassword
591
     * @param int $proxyAuthType
592
     * @param bool $keepAlive
593
     * @param string $key
594
     * @param string $keyPass
595
     * @param int $sslVersion
596
     * @return Response
597
     */
598
    protected function sendPayloadHTTPS($req, $server, $port, $timeout = 0, $username = '',  $password = '',
599
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
600
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $keepAlive = false, $key = '', $keyPass = '',
601
        $sslVersion = 0)
602
    {
603
        return $this->sendPayloadCURL($req, $server, $port, $timeout, $username,
604
            $password, $authType, $cert, $certPass, $caCert, $caCertDir, $proxyHost, $proxyPort,
605
            $proxyUsername, $proxyPassword, $proxyAuthType, 'https', $keepAlive, $key, $keyPass, $sslVersion);
606
    }
607
608
    /**
609
     * @param Request $req
610
     * @param string $server
611
     * @param int $port
612
     * @param int $timeout
613
     * @param string $username
614
     * @param string $password
615
     * @param int $authType only value supported is 1
616
     * @param string $cert
617
     * @param string $certPass
618
     * @param string $caCert
619
     * @param string $caCertDir
620
     * @param string $proxyHost
621
     * @param int $proxyPort
622
     * @param string $proxyUsername
623
     * @param string $proxyPassword
624
     * @param int $proxyAuthType only value supported is 1
625
     * @param string $method 'http' (synonym for 'http10'), 'http10' or 'https'
626
     * @param string $key
627
     * @param string $keyPass @todo not implemented yet.
628
     * @param int $sslVersion @todo not implemented yet. See http://php.net/manual/en/migration56.openssl.php
629
     * @return Response
630
     */
631 344
    protected function sendPayloadSocket($req, $server, $port, $timeout = 0, $username = '', $password = '',
632
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
633
        $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...
634
        $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...
635
    {
636 344
        if ($port == 0) {
637 342
            $port = ( $method === 'https' ) ? 443 : 80;
638 342
        }
639
640
        // Only create the payload if it was not created previously
641 344
        if (empty($req->payload)) {
642 316
            $req->createPayload($this->request_charset_encoding);
643 316
        }
644
645 344
        $payload = $req->payload;
646
        // Deflate request body and set appropriate request headers
647 344
        $encodingHdr = '';
648 344 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 60
            if ($this->request_compression == 'gzip') {
650 30
                $a = @gzencode($payload);
651 30
                if ($a) {
652 30
                    $payload = $a;
653 30
                    $encodingHdr = "Content-Encoding: gzip\r\n";
654 30
                }
655 30
            } else {
656 30
                $a = @gzcompress($payload);
657 30
                if ($a) {
658 30
                    $payload = $a;
659 30
                    $encodingHdr = "Content-Encoding: deflate\r\n";
660 30
                }
661
            }
662 60
        }
663
664
        // thanks to Grant Rauscher <[email protected]> for this
665 344
        $credentials = '';
666 344
        if ($username != '') {
667 30
            $credentials = 'Authorization: Basic ' . base64_encode($username . ':' . $password) . "\r\n";
668 30
            if ($authType != 1) {
669
                error_log('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported with HTTP 1.0');
670
            }
671 30
        }
672
673 344
        $acceptedEncoding = '';
674 344
        if (is_array($this->accepted_compression) && count($this->accepted_compression)) {
675 62
            $acceptedEncoding = 'Accept-Encoding: ' . implode(', ', $this->accepted_compression) . "\r\n";
676 62
        }
677
678 344
        $proxyCredentials = '';
679 344
        if ($proxyHost) {
680 30
            if ($proxyPort == 0) {
681
                $proxyPort = 8080;
682
            }
683 30
            $connectServer = $proxyHost;
684 30
            $connectPort = $proxyPort;
685 30
            $transport = 'tcp';
686 30
            $uri = 'http://' . $server . ':' . $port . $this->path;
687 30
            if ($proxyUsername != '') {
688
                if ($proxyAuthType != 1) {
689
                    error_log('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported with HTTP 1.0');
690
                }
691
                $proxyCredentials = 'Proxy-Authorization: Basic ' . base64_encode($proxyUsername . ':' . $proxyPassword) . "\r\n";
692
            }
693 30
        } else {
694 314
            $connectServer = $server;
695 314
            $connectPort = $port;
696 314
            $transport = ( $method === 'https' ) ? 'tls' : 'tcp';
697 314
            $uri = $this->path;
698
        }
699
700
        // Cookie generation, as per rfc2965 (version 1 cookies) or
701
        // netscape's rules (version 0 cookies)
702 344
        $cookieHeader = '';
703 344
        if (count($this->cookies)) {
704 298
            $version = '';
705 298
            foreach ($this->cookies as $name => $cookie) {
706 298
                if ($cookie['version']) {
707
                    $version = ' $Version="' . $cookie['version'] . '";';
708
                    $cookieHeader .= ' ' . $name . '="' . $cookie['value'] . '";';
709
                    if ($cookie['path']) {
710
                        $cookieHeader .= ' $Path="' . $cookie['path'] . '";';
711
                    }
712
                    if ($cookie['domain']) {
713
                        $cookieHeader .= ' $Domain="' . $cookie['domain'] . '";';
714
                    }
715
                    if ($cookie['port']) {
716
                        $cookieHeader .= ' $Port="' . $cookie['port'] . '";';
717
                    }
718
                } else {
719 298
                    $cookieHeader .= ' ' . $name . '=' . $cookie['value'] . ";";
720
                }
721 298
            }
722 298
            $cookieHeader = 'Cookie:' . $version . substr($cookieHeader, 0, -1) . "\r\n";
723 298
        }
724
725
        // omit port if default
726 344
        if (($port == 80 && in_array($method, array('http', 'http10'))) || ($port == 443 && $method == 'https')) {
727 344
            $port =  '';
728 344
        } else {
729
            $port = ':' . $port;
730
        }
731
732 344
        $op = 'POST ' . $uri . " HTTP/1.0\r\n" .
733 344
            'User-Agent: ' . $this->user_agent . "\r\n" .
734 344
            'Host: ' . $server . $port . "\r\n" .
735 344
            $credentials .
736 344
            $proxyCredentials .
737 344
            $acceptedEncoding .
738 344
            $encodingHdr .
739 344
            'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings) . "\r\n" .
740 344
            $cookieHeader .
741 344
            'Content-Type: ' . $req->content_type . "\r\nContent-Length: " .
742 344
            strlen($payload) . "\r\n\r\n" .
743 344
            $payload;
744
745 344
        if ($this->debug > 1) {
746
            Logger::instance()->debugMessage("---SENDING---\n$op\n---END---");
747
        }
748
749 344
        $contextOptions = array();
750 344
        if ($method == 'https') {
751 30
            if ($cert != '') {
752
                $contextOptions['ssl']['local_cert'] = $cert;
753
                if ($certPass != '') {
754
                    $contextOptions['ssl']['passphrase'] = $certPass;
755
                }
756
            }
757 30
            if ($caCert != '') {
758
                $contextOptions['ssl']['cafile'] = $caCert;
759
            }
760 30
            if ($caCertDir != '') {
761
                $contextOptions['ssl']['capath'] = $caCertDir;
762
            }
763 30
            if ($key != '') {
764
                $contextOptions['ssl']['local_pk'] = $key;
765
            }
766 30
            $contextOptions['ssl']['verify_peer'] = $this->verifypeer;
767 30
            $contextOptions['ssl']['verify_peer_name'] = $this->verifypeer;
768 30
        }
769 344
        $context = stream_context_create($contextOptions);
770
771 344
        if ($timeout <= 0) {
772 44
            $connectTimeout = ini_get('default_socket_timeout');
773 44
        } else {
774 300
            $connectTimeout = $timeout;
775
        }
776
777 344
        $this->errno = 0;
778 344
        $this->errstr = '';
779
780 344
        $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr, $connectTimeout,
781 344
            STREAM_CLIENT_CONNECT, $context);
782 344
        if ($fp) {
783 343
            if ($timeout > 0) {
784 299
                stream_set_timeout($fp, $timeout);
785 299
            }
786 343
        } else {
787 1
            if ($this->errstr == '') {
788
                $err = error_get_last();
789
                $this->errstr = $err['message'];
790
            }
791 1
            $this->errstr = 'Connect error: ' . $this->errstr;
792 1
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr . ' (' . $this->errno . ')');
793
794 1
            return $r;
795
        }
796
797 343
        if (!fputs($fp, $op, strlen($op))) {
798
            fclose($fp);
799
            $this->errstr = 'Write error';
800
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr);
801
802
            return $r;
803
        }
804
805
        // G. Giunta 2005/10/24: close socket before parsing.
806
        // should yield slightly better execution times, and make easier recursive calls (e.g. to follow http redirects)
807 343
        $ipd = '';
808
        do {
809
            // shall we check for $data === FALSE?
810
            // as per the manual, it signals an error
811 343
            $ipd .= fread($fp, 32768);
812 343
        } while (!feof($fp));
813 343
        fclose($fp);
814
815 343
        $r = $req->parseResponse($ipd, false, $this->return_type);
816
817 343
        return $r;
818
    }
819
820
    /**
821
     * Contributed by Justin Miller <[email protected]>
822
     * Requires curl to be built into PHP
823
     * NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers!
824
     *
825
     * @param Request $req
826
     * @param string $server
827
     * @param int $port
828
     * @param int $timeout
829
     * @param string $username
830
     * @param string $password
831
     * @param int $authType
832
     * @param string $cert
833
     * @param string $certPass
834
     * @param string $caCert
835
     * @param string $caCertDir
836
     * @param string $proxyHost
837
     * @param int $proxyPort
838
     * @param string $proxyUsername
839
     * @param string $proxyPassword
840
     * @param int $proxyAuthType
841
     * @param string $method 'http' (let curl decide), 'http10', 'http11' or 'https'
842
     * @param bool $keepAlive
843
     * @param string $key
844
     * @param string $keyPass
845
     * @param int $sslVersion
846
     * @return Response
847
     */
848 242
    protected function sendPayloadCURL($req, $server, $port, $timeout = 0, $username = '', $password = '',
849
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
850
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'https', $keepAlive = false, $key = '',
851
        $keyPass = '', $sslVersion = 0)
852
    {
853 242
        if (!function_exists('curl_init')) {
854
            $this->errstr = 'CURL unavailable on this install';
855
            return new Response(0, PhpXmlRpc::$xmlrpcerr['no_curl'], PhpXmlRpc::$xmlrpcstr['no_curl']);
856
        }
857 242
        if ($method == 'https') {
858 60
            if (($info = curl_version()) &&
859 60
                ((is_string($info) && strpos($info, 'OpenSSL') === null) || (is_array($info) && !isset($info['ssl_version'])))
860 60
            ) {
861
                $this->errstr = 'SSL unavailable on this install';
862
                return new Response(0, PhpXmlRpc::$xmlrpcerr['no_ssl'], PhpXmlRpc::$xmlrpcstr['no_ssl']);
863
            }
864 60
        }
865
866 242
        if ($port == 0) {
867 241
            if (in_array($method, array('http', 'http10', 'http11'))) {
868 181
                $port = 80;
869 181
            } else {
870 60
                $port = 443;
871
            }
872 241
        }
873
874
        // Only create the payload if it was not created previously
875 242
        if (empty($req->payload)) {
876 226
            $req->createPayload($this->request_charset_encoding);
877 226
        }
878
879
        // Deflate request body and set appropriate request headers
880 242
        $payload = $req->payload;
881 242 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...
882 60
            if ($this->request_compression == 'gzip') {
883 30
                $a = @gzencode($payload);
884 30
                if ($a) {
885 30
                    $payload = $a;
886 30
                    $encodingHdr = 'Content-Encoding: gzip';
887 30
                }
888 30
            } else {
889 30
                $a = @gzcompress($payload);
890 30
                if ($a) {
891 30
                    $payload = $a;
892 30
                    $encodingHdr = 'Content-Encoding: deflate';
893 30
                }
894
            }
895 60
        } else {
896 182
            $encodingHdr = '';
897
        }
898
899 242
        if ($this->debug > 1) {
900
            Logger::instance()->debugMessage("---SENDING---\n$payload\n---END---");
901
        }
902
903 242
        if (!$keepAlive || !$this->xmlrpc_curl_handle) {
904 242
            if ($method == 'http11' || $method == 'http10') {
905 30
                $protocol = 'http';
906 30
            } else {
907 212
                $protocol = $method;
908
            }
909 242
            $curl = curl_init($protocol . '://' . $server . ':' . $port . $this->path);
910 242
            if ($keepAlive) {
911 92
                $this->xmlrpc_curl_handle = $curl;
912 92
            }
913 242
        } else {
914 34
            $curl = $this->xmlrpc_curl_handle;
915
        }
916
917
        // results into variable
918 242
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
919
920 242
        if ($this->debug > 1) {
921
            curl_setopt($curl, CURLOPT_VERBOSE, true);
922
            /// @todo allow callers to redirect curlopt_stderr to some stream which can be buffered
923
        }
924 242
        curl_setopt($curl, CURLOPT_USERAGENT, $this->user_agent);
925
        // required for XMLRPC: post the data
926 242
        curl_setopt($curl, CURLOPT_POST, 1);
927
        // the data
928 242
        curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
929
930
        // return the header too
931 242
        curl_setopt($curl, CURLOPT_HEADER, 1);
932
933
        // NB: if we set an empty string, CURL will add http header indicating
934
        // ALL methods it is supporting. This is possibly a better option than
935
        // letting the user tell what curl can / cannot do...
936 242
        if (is_array($this->accepted_compression) && count($this->accepted_compression)) {
937
            //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...
938
            // empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
939 61
            if (count($this->accepted_compression) == 1) {
940 60
                curl_setopt($curl, CURLOPT_ENCODING, $this->accepted_compression[0]);
941 60
            } else {
942 1
                curl_setopt($curl, CURLOPT_ENCODING, '');
943
            }
944 61
        }
945
        // extra headers
946 242
        $headers = array('Content-Type: ' . $req->content_type, 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings));
947
        // if no keepalive is wanted, let the server know it in advance
948 242
        if (!$keepAlive) {
949 150
            $headers[] = 'Connection: close';
950 150
        }
951
        // request compression header
952 242
        if ($encodingHdr) {
953 60
            $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...
954 60
        }
955
956
        // Fix the HTTP/1.1 417 Expectation Failed Bug (curl by default adds a 'Expect: 100-continue' header when POST
957
        // size exceeds 1025 bytes, apparently)
958 242
        $headers[] = 'Expect:';
959
960 242
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
961
        // timeout is borked
962 242
        if ($timeout) {
963 210
            curl_setopt($curl, CURLOPT_TIMEOUT, $timeout == 1 ? 1 : $timeout - 1);
964 210
        }
965
966 242
        if ($method == 'http10') {
967 30
            curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
968 242
        } elseif ($method == 'http11') {
969
            curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
970
        }
971
972 242 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...
973 30
            curl_setopt($curl, CURLOPT_USERPWD, $username . ':' . $password);
974 30
            if (defined('CURLOPT_HTTPAUTH')) {
975 30
                curl_setopt($curl, CURLOPT_HTTPAUTH, $authType);
976 30
            } elseif ($authType != 1) {
977
                error_log('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported by the current PHP/curl install');
978
            }
979 30
        }
980
981 242
        if ($method == 'https') {
982
            // set cert file
983 60
            if ($cert) {
984
                curl_setopt($curl, CURLOPT_SSLCERT, $cert);
985
            }
986
            // set cert password
987 60
            if ($certPass) {
988
                curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $certPass);
989
            }
990
            // whether to verify remote host's cert
991 60
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verifypeer);
992
            // set ca certificates file/dir
993 60
            if ($caCert) {
994
                curl_setopt($curl, CURLOPT_CAINFO, $caCert);
995
            }
996 60
            if ($caCertDir) {
997
                curl_setopt($curl, CURLOPT_CAPATH, $caCertDir);
998
            }
999
            // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1000 60
            if ($key) {
1001
                curl_setopt($curl, CURLOPT_SSLKEY, $key);
1002
            }
1003
            // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1004 60
            if ($keyPass) {
1005
                curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $keyPass);
1006
            }
1007
            // 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
1008 60
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $this->verifyhost);
1009
            // allow usage of different SSL versions
1010 60
            curl_setopt($curl, CURLOPT_SSLVERSION, $sslVersion);
1011 60
        }
1012
1013
        // proxy info
1014 242
        if ($proxyHost) {
1015 60
            if ($proxyPort == 0) {
1016
                $proxyPort = 8080; // NB: even for HTTPS, local connection is on port 8080
1017
            }
1018 60
            curl_setopt($curl, CURLOPT_PROXY, $proxyHost . ':' . $proxyPort);
1019 60 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...
1020
                curl_setopt($curl, CURLOPT_PROXYUSERPWD, $proxyUsername . ':' . $proxyPassword);
1021
                if (defined('CURLOPT_PROXYAUTH')) {
1022
                    curl_setopt($curl, CURLOPT_PROXYAUTH, $proxyAuthType);
1023
                } elseif ($proxyAuthType != 1) {
1024
                    error_log('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported by the current PHP/curl install');
1025
                }
1026
            }
1027 60
        }
1028
1029
        // NB: should we build cookie http headers by hand rather than let CURL do it?
1030
        // the following code does not honour 'expires', 'path' and 'domain' cookie attributes
1031
        // set to client obj the the user...
1032 242
        if (count($this->cookies)) {
1033 209
            $cookieHeader = '';
1034 209
            foreach ($this->cookies as $name => $cookie) {
1035 209
                $cookieHeader .= $name . '=' . $cookie['value'] . '; ';
1036 209
            }
1037 209
            curl_setopt($curl, CURLOPT_COOKIE, substr($cookieHeader, 0, -2));
1038 209
        }
1039
1040 242
        foreach ($this->extracurlopts as $opt => $val) {
1041
            curl_setopt($curl, $opt, $val);
1042 242
        }
1043
1044 242
        $result = curl_exec($curl);
1045
1046 242
        if ($this->debug > 1) {
1047
            $message = "---CURL INFO---\n";
1048
            foreach (curl_getinfo($curl) as $name => $val) {
1049
                if (is_array($val)) {
1050
                    $val = implode("\n", $val);
1051
                }
1052
                $message .= $name . ': ' . $val . "\n";
1053
            }
1054
            $message .= '---END---';
1055
            Logger::instance()->debugMessage($message);
1056
        }
1057
1058 242
        if (!$result) {
1059
            /// @todo we should use a better check here - what if we get back '' or '0'?
1060
1061 1
            $this->errstr = 'no response';
1062 1
            $resp = new Response(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] . ': ' . curl_error($curl));
1063 1
            curl_close($curl);
1064 1
            if ($keepAlive) {
1065 1
                $this->xmlrpc_curl_handle = null;
1066 1
            }
1067 1
        } else {
1068 242
            if (!$keepAlive) {
1069 150
                curl_close($curl);
1070 150
            }
1071 242
            $resp = $req->parseResponse($result, true, $this->return_type);
1072
            // if we got back a 302, we can not reuse the curl handle for later calls
1073 242
            if ($resp->faultCode() == PhpXmlRpc::$xmlrpcerr['http_error'] && $keepAlive) {
1074
                curl_close($curl);
1075
                $this->xmlrpc_curl_handle = null;
1076
            }
1077
        }
1078
1079 242
        return $resp;
1080
    }
1081
1082
    /**
1083
     * Send an array of requests and return an array of responses.
1084
     *
1085
     * Unless $this->no_multicall has been set to true, it will try first to use one single xmlrpc call to server method
1086
     * system.multicall, and revert to sending many successive calls in case of failure.
1087
     * This failure is also stored in $this->no_multicall for subsequent calls.
1088
     * Unfortunately, there is no server error code universally used to denote the fact that multicall is unsupported,
1089
     * so there is no way to reliably distinguish between that and a temporary failure.
1090
     * If you are sure that server supports multicall and do not want to fallback to using many single calls, set the
1091
     * fourth parameter to FALSE.
1092
     *
1093
     * NB: trying to shoehorn extra functionality into existing syntax has resulted
1094
     * in pretty much convoluted code...
1095
     *
1096
     * @param Request[] $reqs an array of Request objects
1097
     * @param integer $timeout connection timeout (in seconds). See the details in the docs for the send() method
1098
     * @param string $method the http protocol variant to be used. See the details in the docs for the send() method
1099
     * @param boolean fallback When true, upon receiving an error during multicall, multiple single calls will be
1100
     *                         attempted
1101
     *
1102
     * @return Response[]
1103
     */
1104 58
    public function multicall($reqs, $timeout = 0, $method = '', $fallback = true)
1105
    {
1106 58
        if ($method == '') {
1107
            $method = $this->method;
1108
        }
1109 58
        if (!$this->no_multicall) {
1110 39
            $results = $this->_try_multicall($reqs, $timeout, $method);
1111 39
            if (is_array($results)) {
1112
                // System.multicall succeeded
1113 39
                return $results;
1114
            } else {
1115
                // either system.multicall is unsupported by server,
1116
                // or call failed for some other reason.
1117 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...
1118
                    // Don't try it next time...
1119
                    $this->no_multicall = true;
1120
                } else {
1121
                    if (is_a($results, '\PhpXmlRpc\Response')) {
1122
                        $result = $results;
1123
                    } else {
1124
                        $result = new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'], PhpXmlRpc::$xmlrpcstr['multicall_error']);
1125
                    }
1126
                }
1127
            }
1128
        } else {
1129
            // override fallback, in case careless user tries to do two
1130
            // opposite things at the same time
1131 20
            $fallback = true;
1132
        }
1133
1134 20
        $results = array();
1135 20
        if ($fallback) {
1136
            // system.multicall is (probably) unsupported by server:
1137
            // emulate multicall via multiple requests
1138 20
            foreach ($reqs as $req) {
1139 20
                $results[] = $this->send($req, $timeout, $method);
1140 20
            }
1141 20
        } else {
1142
            // user does NOT want to fallback on many single calls:
1143
            // since we should always return an array of responses,
1144
            // return an array with the same error repeated n times
1145
            foreach ($reqs as $req) {
1146
                $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...
1147
            }
1148
        }
1149
1150 20
        return $results;
1151
    }
1152
1153
    /**
1154
     * Attempt to boxcar $reqs via system.multicall.
1155
     *
1156
     * Returns either an array of Response, a single error Response or false (when received response does not respect
1157
     * valid multicall syntax).
1158
     *
1159
     * @param Request[] $reqs
1160
     * @param int $timeout
1161
     * @param string $method
1162
     * @return Response[]|bool|mixed|Response
1163
     */
1164 39
    private function _try_multicall($reqs, $timeout, $method)
1165
    {
1166
        // Construct multicall request
1167 39
        $calls = array();
1168 39
        foreach ($reqs as $req) {
1169 39
            $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...
1170 39
            $numParams = $req->getNumParams();
1171 39
            $params = array();
1172 39
            for ($i = 0; $i < $numParams; $i++) {
1173 39
                $params[$i] = $req->getParam($i);
1174 39
            }
1175 39
            $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...
1176 39
            $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...
1177 39
        }
1178 39
        $multiCall = new Request('system.multicall');
1179 39
        $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...
1180
1181
        // Attempt RPC call
1182 39
        $result = $this->send($multiCall, $timeout, $method);
1183
1184 39
        if ($result->faultCode() != 0) {
1185
            // call to system.multicall failed
1186
            return $result;
1187
        }
1188
1189
        // Unpack responses.
1190 39
        $rets = $result->value();
1191
1192 39
        if ($this->return_type == 'xml') {
1193
            return $rets;
1194 39
        } elseif ($this->return_type == 'phpvals') {
1195
            /// @todo test this code branch...
1196 20
            $rets = $result->value();
1197 20
            if (!is_array($rets)) {
1198
                return false;       // bad return type from system.multicall
1199
            }
1200 20
            $numRets = count($rets);
1201 20
            if ($numRets != count($reqs)) {
1202
                return false;       // wrong number of return values.
1203
            }
1204
1205 20
            $response = array();
1206 20
            for ($i = 0; $i < $numRets; $i++) {
1207 20
                $val = $rets[$i];
1208 20
                if (!is_array($val)) {
1209
                    return false;
1210
                }
1211 20
                switch (count($val)) {
1212 20
                    case 1:
1213 20
                        if (!isset($val[0])) {
1214
                            return false;       // Bad value
1215
                        }
1216
                        // Normal return value
1217 20
                        $response[$i] = new Response($val[0], 0, '', 'phpvals');
1218 20
                        break;
1219 20
                    case 2:
1220
                        /// @todo remove usage of @: it is apparently quite slow
1221 20
                        $code = @$val['faultCode'];
1222 20
                        if (!is_int($code)) {
1223
                            return false;
1224
                        }
1225 20
                        $str = @$val['faultString'];
1226 20
                        if (!is_string($str)) {
1227
                            return false;
1228
                        }
1229 20
                        $response[$i] = new Response(0, $code, $str);
1230 20
                        break;
1231
                    default:
1232
                        return false;
1233 20
                }
1234 20
            }
1235
1236 20
            return $response;
1237
        } else {
1238
            // return type == 'xmlrpcvals'
1239
1240 20
            $rets = $result->value();
1241 20
            if ($rets->kindOf() != 'array') {
1242
                return false;       // bad return type from system.multicall
1243
            }
1244 20
            $numRets = $rets->count();
1245 20
            if ($numRets != count($reqs)) {
1246
                return false;       // wrong number of return values.
1247
            }
1248
1249 20
            $response = array();
1250 20
            foreach($rets as $val) {
1251 20
                switch ($val->kindOf()) {
1252 20
                    case 'array':
1253 20
                        if ($val->count() != 1) {
1254
                            return false;       // Bad value
1255
                        }
1256
                        // Normal return value
1257 20
                        $response[] = new Response($val[0]);
1258 20
                        break;
1259 20
                    case 'struct':
1260 20
                        $code = $val['faultCode'];
1261
                        /** @var Value $code */
1262 20
                        if ($code->kindOf() != 'scalar' || $code->scalartyp() != 'int') {
1263
                            return false;
1264
                        }
1265 20
                        $str = $val['faultString'];
1266
                        /** @var Value $str */
1267 20
                        if ($str->kindOf() != 'scalar' || $str->scalartyp() != 'string') {
1268
                            return false;
1269
                        }
1270 20
                        $response[] = new Response(0, $code->scalarval(), $str->scalarval());
1271 20
                        break;
1272
                    default:
1273
                        return false;
1274 20
                }
1275 20
            }
1276
1277 20
            return $response;
1278
        }
1279
    }
1280
}
1281