Passed
Push — master ( cbba09...09dbc1 )
by Gaetano
07:09
created

Client::sendPayloadHTTP10()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1120
            $resp = $req->parseResponse(/** @scrutinizer ignore-type */ $result, true, $this->return_type);
Loading history...
1121
            // if we got back a 302, we can not reuse the curl handle for later calls
1122 322
            if ($resp->faultCode() == PhpXmlRpc::$xmlrpcerr['http_error'] && $keepAlive) {
1123
                curl_close($curl);
1124
                $this->xmlrpc_curl_handle = null;
1125
            }
1126
        }
1127
1128 322
        return $resp;
1129
    }
1130
1131
    /**
1132
     * Send an array of requests and return an array of responses.
1133
     *
1134
     * Unless $this->no_multicall has been set to true, it will try first to use one single xmlrpc call to server method
1135
     * system.multicall, and revert to sending many successive calls in case of failure.
1136
     * This failure is also stored in $this->no_multicall for subsequent calls.
1137
     * Unfortunately, there is no server error code universally used to denote the fact that multicall is unsupported,
1138
     * so there is no way to reliably distinguish between that and a temporary failure.
1139
     * If you are sure that server supports multicall and do not want to fallback to using many single calls, set the
1140
     * fourth parameter to FALSE.
1141
     *
1142
     * NB: trying to shoehorn extra functionality into existing syntax has resulted
1143
     * in pretty much convoluted code...
1144
     *
1145
     * @param Request[] $reqs an array of Request objects
1146
     * @param integer $timeout connection timeout (in seconds). See the details in the docs for the send() method
1147
     * @param string $method the http protocol variant to be used. See the details in the docs for the send() method
1148
     * @param boolean fallback When true, upon receiving an error during multicall, multiple single calls will be
0 ignored issues
show
Bug introduced by
The type PhpXmlRpc\fallback was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
1149
     *                         attempted
1150
     *
1151
     * @return Response[]
1152
     */
1153 65
    public function multicall($reqs, $timeout = 0, $method = '', $fallback = true)
1154
    {
1155 65
        if ($method == '') {
1156
            $method = $this->method;
1157
        }
1158 65
        if (!$this->no_multicall) {
1159 44
            $results = $this->_try_multicall($reqs, $timeout, $method);
1160 44
            if (is_array($results)) {
1161
                // System.multicall succeeded
1162 44
                return $results;
1163
            } else {
1164
                // either system.multicall is unsupported by server,
1165
                // or call failed for some other reason.
1166
                if ($fallback) {
1167
                    // Don't try it next time...
1168
                    $this->no_multicall = true;
1169
                } else {
1170
                    if (is_a($results, '\PhpXmlRpc\Response')) {
1171
                        $result = $results;
1172
                    } else {
1173
                        $result = new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'], PhpXmlRpc::$xmlrpcstr['multicall_error']);
1174
                    }
1175
                }
1176
            }
1177
        } else {
1178
            // override fallback, in case careless user tries to do two
1179
            // opposite things at the same time
1180 22
            $fallback = true;
1181
        }
1182
1183 22
        $results = array();
1184 22
        if ($fallback) {
1185
            // system.multicall is (probably) unsupported by server:
1186
            // emulate multicall via multiple requests
1187 22
            foreach ($reqs as $req) {
1188 22
                $results[] = $this->send($req, $timeout, $method);
1189
            }
1190
        } else {
1191
            // user does NOT want to fallback on many single calls:
1192
            // since we should always return an array of responses,
1193
            // return an array with the same error repeated n times
1194
            foreach ($reqs as $req) {
1195
                $results[] = $result;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.
Loading history...
1196
            }
1197
        }
1198
1199 22
        return $results;
1200
    }
1201
1202
    /**
1203
     * Attempt to boxcar $reqs via system.multicall.
1204
     *
1205
     * Returns either an array of Response, a single error Response or false (when received response does not respect
1206
     * valid multicall syntax).
1207
     *
1208
     * @param Request[] $reqs
1209
     * @param int $timeout
1210
     * @param string $method
1211
     * @return Response[]|bool|mixed|Response
1212
     */
1213 44
    private function _try_multicall($reqs, $timeout, $method)
1214
    {
1215
        // Construct multicall request
1216 44
        $calls = array();
1217 44
        foreach ($reqs as $req) {
1218 44
            $call['methodName'] = new Value($req->method(), 'string');
1219 44
            $numParams = $req->getNumParams();
1220 44
            $params = array();
1221 44
            for ($i = 0; $i < $numParams; $i++) {
1222 44
                $params[$i] = $req->getParam($i);
1223
            }
1224 44
            $call['params'] = new Value($params, 'array');
1225 44
            $calls[] = new Value($call, 'struct');
1226
        }
1227 44
        $multiCall = new Request('system.multicall');
1228 44
        $multiCall->addParam(new Value($calls, 'array'));
1229
1230
        // Attempt RPC call
1231 44
        $result = $this->send($multiCall, $timeout, $method);
1232
1233 44
        if ($result->faultCode() != 0) {
1234
            // call to system.multicall failed
1235
            return $result;
1236
        }
1237
1238
        // Unpack responses.
1239 44
        $rets = $result->value();
1240
1241 44
        if ($this->return_type == 'xml') {
1242
            return $rets;
1243 44
        } elseif ($this->return_type == 'phpvals') {
1244
            /// @todo test this code branch...
1245 22
            $rets = $result->value();
1246 22
            if (!is_array($rets)) {
0 ignored issues
show
introduced by
The condition is_array($rets) is always false.
Loading history...
1247
                return false;       // bad return type from system.multicall
1248
            }
1249 22
            $numRets = count($rets);
1250 22
            if ($numRets != count($reqs)) {
1251
                return false;       // wrong number of return values.
1252
            }
1253
1254 22
            $response = array();
1255 22
            for ($i = 0; $i < $numRets; $i++) {
1256 22
                $val = $rets[$i];
1257 22
                if (!is_array($val)) {
1258
                    return false;
1259
                }
1260 22
                switch (count($val)) {
1261 22
                    case 1:
1262 22
                        if (!isset($val[0])) {
1263
                            return false;       // Bad value
1264
                        }
1265
                        // Normal return value
1266 22
                        $response[$i] = new Response($val[0], 0, '', 'phpvals');
1267 22
                        break;
1268 22
                    case 2:
1269
                        /// @todo remove usage of @: it is apparently quite slow
1270 22
                        $code = @$val['faultCode'];
1271 22
                        if (!is_int($code)) {
1272
                            return false;
1273
                        }
1274 22
                        $str = @$val['faultString'];
1275 22
                        if (!is_string($str)) {
1276
                            return false;
1277
                        }
1278 22
                        $response[$i] = new Response(0, $code, $str);
1279 22
                        break;
1280
                    default:
1281
                        return false;
1282
                }
1283
            }
1284
1285 22
            return $response;
1286
        } else {
1287
            // return type == 'xmlrpcvals'
1288
1289 23
            $rets = $result->value();
1290 23
            if ($rets->kindOf() != 'array') {
0 ignored issues
show
Bug introduced by
The method kindOf() does not exist on integer. ( Ignorable by Annotation )

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

1290
            if ($rets->/** @scrutinizer ignore-call */ kindOf() != 'array') {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1291
                return false;       // bad return type from system.multicall
1292
            }
1293 23
            $numRets = $rets->count();
1294 23
            if ($numRets != count($reqs)) {
1295
                return false;       // wrong number of return values.
1296
            }
1297
1298 23
            $response = array();
1299 23
            foreach($rets as $val) {
0 ignored issues
show
Bug introduced by
The expression $rets of type integer is not traversable.
Loading history...
1300 23
                switch ($val->kindOf()) {
1301 23
                    case 'array':
1302 23
                        if ($val->count() != 1) {
1303
                            return false;       // Bad value
1304
                        }
1305
                        // Normal return value
1306 23
                        $response[] = new Response($val[0]);
1307 23
                        break;
1308 22
                    case 'struct':
1309 22
                        $code = $val['faultCode'];
1310
                        /** @var Value $code */
1311 22
                        if ($code->kindOf() != 'scalar' || $code->scalartyp() != 'int') {
1312
                            return false;
1313
                        }
1314 22
                        $str = $val['faultString'];
1315
                        /** @var Value $str */
1316 22
                        if ($str->kindOf() != 'scalar' || $str->scalartyp() != 'string') {
1317
                            return false;
1318
                        }
1319 22
                        $response[] = new Response(0, $code->scalarval(), $str->scalarval());
1320 22
                        break;
1321
                    default:
1322
                        return false;
1323
                }
1324
            }
1325
1326 23
            return $response;
1327
        }
1328
    }
1329
}
1330