Client::send()   F
last analyzed

Complexity

Conditions 16
Paths 392

Size

Total Lines 90
Code Lines 63

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 23.8125

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 16
eloc 63
c 3
b 0
f 0
nc 392
nop 3
dl 0
loc 90
ccs 22
cts 32
cp 0.6875
crap 23.8125
rs 2.3333

How to fix   Long Method    Complexity   

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\Exception\ValueErrorException;
6
use PhpXmlRpc\Helper\XMLParser;
7
use PhpXmlRpc\Traits\CharsetEncoderAware;
8
use PhpXmlRpc\Traits\DeprecationLogger;
9
10
/**
11
 * Used to represent a client of an XML-RPC server.
12
 *
13
 * @property int $errno deprecated - public access left in purely for BC.
14
 * @property string $errstr deprecated - public access left in purely for BC.
15
 * @property string $method deprecated - public access left in purely for BC. Access via getUrl()/__construct()
16
 * @property string $server deprecated - public access left in purely for BC. Access via getUrl()/__construct()
17
 * @property int $port deprecated - public access left in purely for BC. Access via getUrl()/__construct()
18
 * @property string $path deprecated - public access left in purely for BC. Access via getUrl()/__construct()
19
 */
20
class Client
21
{
22
    use DeprecationLogger;
23
    //use CharsetEncoderAware;
24
25
    const USE_CURL_NEVER = 0;
26
    const USE_CURL_ALWAYS = 1;
27
    const USE_CURL_AUTO = 2;
28
29
    const OPT_ACCEPTED_CHARSET_ENCODINGS = 'accepted_charset_encodings';
30
    const OPT_ACCEPTED_COMPRESSION = 'accepted_compression';
31
    const OPT_AUTH_TYPE = 'authtype';
32
    const OPT_CA_CERT = 'cacert';
33
    const OPT_CA_CERT_DIR = 'cacertdir';
34
    const OPT_CERT = 'cert';
35
    const OPT_CERT_PASS = 'certpass';
36
    const OPT_COOKIES = 'cookies';
37
    const OPT_DEBUG = 'debug';
38
    const OPT_EXTRA_CURL_OPTS = 'extracurlopts';
39
    const OPT_EXTRA_SOCKET_OPTS = 'extrasockopts';
40
    const OPT_KEEPALIVE = 'keepalive';
41
    const OPT_KEY = 'key';
42
    const OPT_KEY_PASS = 'keypass';
43
    const OPT_NO_MULTICALL = 'no_multicall';
44
    const OPT_PASSWORD = 'password';
45
    const OPT_PROXY = 'proxy';
46
    const OPT_PROXY_AUTH_TYPE = 'proxy_authtype';
47
    const OPT_PROXY_PASS = 'proxy_pass';
48
    const OPT_PROXY_PORT = 'proxyport';
49
    const OPT_PROXY_USER = 'proxy_user';
50
    const OPT_REQUEST_CHARSET_ENCODING = 'request_charset_encoding';
51
    const OPT_REQUEST_COMPRESSION = 'request_compression';
52
    const OPT_RETURN_TYPE = 'return_type';
53
    const OPT_SSL_VERSION = 'sslversion';
54
    const OPT_TIMEOUT = 'timeout';
55
    const OPT_USERNAME = 'username';
56
    const OPT_USER_AGENT = 'user_agent';
57
    const OPT_USE_CURL = 'use_curl';
58
    const OPT_VERIFY_HOST = 'verifyhost';
59
    const OPT_VERIFY_PEER = 'verifypeer';
60
    const OPT_EXTRA_HEADERS = 'extra_headers';
61
62
    /** @var string */
63
    protected static $requestClass = '\\PhpXmlRpc\\Request';
64
    /** @var string */
65
    protected static $responseClass = '\\PhpXmlRpc\\Response';
66
67
    /**
68
     * @var int
69
     * @deprecated will be removed in the future
70
     */
71
    protected $errno;
72
    /**
73
     * @var string
74
     * @deprecated will be removed in the future
75
     */
76
    protected $errstr;
77
78
    /// @todo: do all the ones below need to be public?
79
80
    /**
81
     * @var string
82
     */
83
    protected $method = 'http';
84
    /**
85
     * @var string
86
     */
87
    protected $server;
88
    /**
89
     * @var int
90
     */
91
    protected $port = 0;
92
    /**
93
     * @var string
94
     */
95
    protected $path;
96
97
    /**
98
     * @var int
99
     */
100
    protected $debug = 0;
101
    /**
102
     * @var string
103
     */
104
    protected $username = '';
105
    /**
106
     * @var string
107
     */
108
    protected $password = '';
109
    /**
110
     * @var int
111
     */
112
    protected $authtype = 1;
113
    /**
114
     * @var string
115
     */
116
    protected $cert = '';
117
    /**
118
     * @var string
119
     */
120
    protected $certpass = '';
121
    /**
122 2
     * @var string
123
     */
124 2
    protected $cacert = '';
125 2
    /**
126
     * @var string
127 2
     */
128
    protected $cacertdir = '';
129
    /**
130
     * @var string
131
     */
132
    protected $key = '';
133
    /**
134
     * @var string
135
     */
136
    protected $keypass = '';
137
    /**
138
     * @var bool
139
     */
140
    protected $verifypeer = true;
141
    /**
142
     * @var int
143
     */
144
    protected $verifyhost = 2;
145
    /**
146
     * @var int
147
     */
148
    protected $sslversion = 0; // corresponds to CURL_SSLVERSION_DEFAULT. Other  CURL_SSLVERSION_ values are supported
149
    /**
150
     * @var string
151
     */
152 718
    protected $proxy = '';
153
    /**
154
     * @var int
155 718
     */
156 7
    protected $proxyport = 0;
157 7
    /**
158 7
     * @var string
159 7
     */
160
    protected $proxy_user = '';
161
    /**
162 7
     * @var string
163
     */
164
    protected $proxy_pass = '';
165 7
    /**
166
     * @var int
167
     */
168 7
    protected $proxy_authtype = 1;
169 7
    /**
170
     * @var array
171 7
     */
172
    protected $cookies = array();
173
    /**
174 7
     * @var array
175
     */
176
    protected $extrasockopts = array();
177
    /**
178 718
     * @var array
179
     */
180
    protected $extracurlopts = array();
181 718
    /**
182
     * @var int
183 718
     */
184 718
    protected $timeout = 0;
185 3
    /**
186
     * @var int
187 718
     */
188 7
    protected $use_curl = self::USE_CURL_AUTO;
189
    /**
190
     * @var bool
191
     *
192 718
     * This determines whether the multicall() method will try to take advantage of the system.multicall xml-rpc method
193
     * to dispatch to the server an array of requests in a single http roundtrip or simply execute many consecutive http
194 711
     * calls. Defaults to FALSE, but it will be enabled automatically on the first failure of execution of
195
     * system.multicall.
196
     */
197 718
    protected $no_multicall = false;
198
    /**
199
     * @var array
200
     *
201 718
     * List of http compression methods accepted by the client for responses.
202
     * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib.
203
     *
204 718
     * 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
205
     * decide the compression methods it supports. You might check for the presence of 'zlib' in the output of
206
     * curl_version() to determine whether compression is supported or not
207
     */
208
    protected $accepted_compression = array();
209
    /**
210
     * @var string|null
211
     *
212
     * Name of compression scheme to be used for sending requests.
213
     * Either null, 'gzip' or 'deflate'.
214
     */
215
    protected $request_compression = '';
216
    /**
217 718
     * @var bool
218 718
     *
219
     * Whether to use persistent connections for http 1.1 and https. Value set at constructor time.
220
     */
221
    protected $keepalive = false;
222
    /**
223
     * @var string[]
224
     *
225
     * Charset encodings that can be decoded without problems by the client. Value set at constructor time
226
     */
227
    protected $accepted_charset_encodings = array();
228
    /**
229
     * @var string
230
     *
231
     * The charset encoding that will be used for serializing request sent by the client.
232
     * It defaults to NULL, which means using US-ASCII and encoding all characters outside the ASCII printable range
233 714
     * using their xml character entity representation (this has the benefit that line end characters will not be mangled
234
     * in the transfer, a CR-LF will be preserved as well as a singe LF).
235 714
     * Valid values are 'US-ASCII', 'UTF-8' and 'ISO-8859-1'.
236 714
     * For the fastest mode of operation, set your both your app internal encoding and this to UTF-8.
237
     */
238
    protected $request_charset_encoding = '';
239
    /**
240
     * @var string
241
     *
242
     * Decides the content of Response objects returned by calls to send() and multicall().
243
     * Valid values are 'xmlrpcvals', 'phpvals' or 'xml'.
244
     *
245
     * Determines whether the value returned inside a Response object as results of calls to the send() and multicall()
246
     * methods will be a Value object, a plain php value or a raw xml string.
247
     * Allowed values are 'xmlrpcvals' (the default), 'phpvals' and 'xml'.
248
     * To allow the user to differentiate between a correct and a faulty response, fault responses will be returned as
249
     * Response objects in any case.
250
     * Note that the 'phpvals' setting will yield faster execution times, but some of the information from the original
251
     * response will be lost. It will be e.g. impossible to tell whether a particular php string value was sent by the
252 66
     * server as an xml-rpc string or base64 value.
253
     */
254 66
    protected $return_type = XMLParser::RETURN_XMLRPCVALS;
255 66
    /**
256 66
     * @var string
257 66
     *
258
     * Sent to servers in http headers. Value set at constructor time.
259
     */
260
    protected $user_agent;
261
262
    /**
263
     * Additional headers to be included in the requests.
264
     *
265
     * @var string[]
266
     */
267
    protected $extra_headers = array();
268
269
    /**
270
     * CURL handle: used for keep-alive
271
     * @internal
272
     */
273
    public $xmlrpc_curl_handle = null;
274
275
    /**
276
     * @var array
277
     */
278
    protected static $options = array(
279
        self::OPT_ACCEPTED_CHARSET_ENCODINGS,
280
        self::OPT_ACCEPTED_COMPRESSION,
281
        self::OPT_AUTH_TYPE,
282
        self::OPT_CA_CERT,
283
        self::OPT_CA_CERT_DIR,
284
        self::OPT_CERT,
285
        self::OPT_CERT_PASS,
286
        self::OPT_COOKIES,
287
        self::OPT_DEBUG,
288
        self::OPT_EXTRA_CURL_OPTS,
289
        self::OPT_EXTRA_SOCKET_OPTS,
290
        self::OPT_KEEPALIVE,
291
        self::OPT_KEY,
292
        self::OPT_KEY_PASS,
293
        self::OPT_NO_MULTICALL,
294
        self::OPT_PASSWORD,
295
        self::OPT_PROXY,
296
        self::OPT_PROXY_AUTH_TYPE,
297
        self::OPT_PROXY_PASS,
298
        self::OPT_PROXY_USER,
299
        self::OPT_PROXY_PORT,
300
        self::OPT_REQUEST_CHARSET_ENCODING,
301
        self::OPT_REQUEST_COMPRESSION,
302
        self::OPT_RETURN_TYPE,
303
        self::OPT_SSL_VERSION,
304
        self::OPT_TIMEOUT,
305
        self::OPT_USE_CURL,
306
        self::OPT_USER_AGENT,
307
        self::OPT_USERNAME,
308
        self::OPT_VERIFY_HOST,
309
        self::OPT_VERIFY_PEER,
310
        self::OPT_EXTRA_HEADERS,
311
    );
312
313
    /**
314
     * @param string $path either the PATH part of the xml-rpc server URL, or complete server URL (in which case you
315
     *                     should use an empty string for all other parameters)
316 132
     *                     e.g. /xmlrpc/server.php
317
     *                     e.g. http://phpxmlrpc.sourceforge.net/server.php
318 132
     *                     e.g. https://james:[email protected]:444/xmlrpcserver?agent=007
319 132
     *                     e.g. h2://fast-and-secure-services.org/endpoint
320
     * @param string $server the server name / ip address
321
     * @param integer $port the port the server is listening on, when omitted defaults to 80 or 443 depending on
322
     *                      protocol used
323
     * @param string $method the http protocol variant: defaults to 'http'; 'https', 'http11', 'h2' and 'h2c' can
324
     *                       be used if CURL is installed. The value set here can be overridden in any call to $this->send().
325
     *                       Use 'h2' to make the lib attempt to use http/2 over a secure connection, and 'h2c'
326
     *                       for http/2 without tls. Note that 'h2c' will not use the h2c 'upgrade' method, and be
327
     *                       thus incompatible with any server/proxy not supporting http/2. This is because POST
328 132
     *                       request are not compatible with h2c upgrade.
329
     */
330 132
    public function __construct($path, $server = '', $port = '', $method = '')
331 132
    {
332
        // allow user to specify all params in $path
333
        if ($server == '' && $port == '' && $method == '') {
334
            $parts = parse_url($path);
335
            $server = $parts['host'];
336
            $path = isset($parts['path']) ? $parts['path'] : '';
337
            if (isset($parts['query'])) {
338 132
                $path .= '?' . $parts['query'];
339
            }
340 132
            if (isset($parts['fragment'])) {
341 132
                $path .= '#' . $parts['fragment'];
342
            }
343
            if (isset($parts['port'])) {
344
                $port = $parts['port'];
345
            }
346
            if (isset($parts['scheme'])) {
347
                $method = $parts['scheme'];
348
            }
349
            if (isset($parts['user'])) {
350
                $this->username = $parts['user'];
351
            }
352
            if (isset($parts['pass'])) {
353
                $this->password = $parts['pass'];
354
            }
355 99
        }
356
        if ($path == '' || $path[0] != '/') {
357 99
            $this->path = '/' . $path;
358 99
        } else {
359 99
            $this->path = $path;
360 99
        }
361 99
        $this->server = $server;
362 99
        if ($port != '') {
363
            $this->port = $port;
364
        }
365
        if ($method != '') {
366
            $this->method = $method;
367
        }
368
369
        // if ZLIB is enabled, let the client by default accept compressed responses
370
        if (function_exists('gzinflate') || (
371
                function_exists('curl_version') && (($info = curl_version()) &&
372
                    ((is_string($info) && strpos($info, 'zlib') !== null) || isset($info['libz_version'])))
373
            )
374
        ) {
375
            $this->accepted_compression = array('gzip', 'deflate');
376
        }
377
378
        // keepalives: enabled by default
379
        $this->keepalive = true;
380
381
        // by default the xml parser can support these 3 charset encodings
382
        $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII');
383
384
        // NB: this is disabled to avoid making all the requests sent huge... mbstring supports more than 80 charsets!
385
        //$ch = $this->getCharsetEncoder();
386
        //$this->accepted_charset_encodings = $ch->knownCharsets();
387
388
        // initialize user_agent string
389
        $this->user_agent = PhpXmlRpc::$xmlrpcName . ' ' . PhpXmlRpc::$xmlrpcVersion;
390
    }
391
392
    /**
393
     * @param string $name see all the OPT_ constants
394
     * @param mixed $value
395
     * @return $this
396
     * @throws ValueErrorException on unsupported option
397
     */
398
    public function setOption($name, $value)
399
    {
400
        if (in_array($name, static::$options)) {
401
            $this->$name = $value;
402
            return $this;
403
        }
404
405
        throw new ValueErrorException("Unsupported option '$name'");
406
    }
407
408
    /**
409
     * @param string $name see all the OPT_ constants
410
     * @return mixed
411
     * @throws ValueErrorException on unsupported option
412
     */
413
    public function getOption($name)
414
    {
415
        if (in_array($name, static::$options)) {
416
            return $this->$name;
417
        }
418 580
419
        throw new ValueErrorException("Unsupported option '$name'");
420 580
    }
421 580
422
    /**
423
     * Returns the complete list of Client options, with their value.
424
     * @return array
425
     */
426
    public function getOptions()
427 580
    {
428
        $values = array();
429 580
        foreach (static::$options as $opt) {
430
            $values[$opt] = $this->getOption($opt);
431
        }
432
        return $values;
433
    }
434
435
    /**
436
     * @param array $options key: any valid option (see all the OPT_ constants)
437
     * @return $this
438
     * @throws ValueErrorException on unsupported option
439
     */
440
    public function setOptions($options)
441
    {
442
        foreach ($options as $name => $value) {
443
            $this->setOption($name, $value);
444
        }
445
446 66
        return $this;
447
    }
448 66
449 66
    /**
450
     * Enable/disable the echoing to screen of the xml-rpc responses received. The default is not to output anything.
451
     *
452
     * The debugging information at level 1 includes the raw data returned from the XML-RPC server it was querying
453
     * (including bot HTTP headers and the full XML payload), and the PHP value the client attempts to create to
454
     * represent the value returned by the server.
455
     * At level 2, the complete payload of the xml-rpc request is also printed, before being sent to the server.
456
     * At level -1, the Response objects returned by send() calls will not carry information about the http response's
457
     * cookies, headers and body, which might save some memory
458
     *
459
     * This option can be very useful when debugging servers as it allows you to see exactly what the client sends and
460
     * the server returns. Never leave it enabled for production!
461
     *
462
     * @param integer $level values -1, 0, 1 and 2 are supported
463
     * @return $this
464
     */
465
    public function setDebug($level)
466
    {
467
        $this->debug = $level;
468
        return $this;
469
    }
470
471
    /**
472
     * Sets the username and password for authorizing the client to the server.
473
     *
474
     * With the default (HTTP) transport, this information is used for HTTP Basic authorization.
475
     * Note that username and password can also be set using the class constructor.
476
     * With HTTP 1.1 and HTTPS transport, NTLM and Digest authentication protocols are also supported. To enable them use
477
     * the constants CURLAUTH_DIGEST and CURLAUTH_NTLM as values for the auth type parameter.
478
     *
479
     * @param string $user username
480
     * @param string $password password
481
     * @param integer $authType auth type. See curl_setopt man page for supported auth types. Defaults to CURLAUTH_BASIC
482
     *                          (basic auth). Note that auth types NTLM and Digest will only work if the Curl php
483
     *                          extension is enabled.
484
     * @return $this
485
     */
486
    public function setCredentials($user, $password, $authType = 1)
487
    {
488
        $this->username = $user;
489
        $this->password = $password;
490
        $this->authtype = $authType;
491
        return $this;
492
    }
493
494
    /**
495 697
     * Set the optional certificate and passphrase used in SSL-enabled communication with a remote server.
496
     *
497
     * Note: to retrieve information about the client certificate on the server side, you will need to look into the
498
     * environment variables which are set up by the webserver. Different webservers will typically set up different
499 697
     * variables.
500 117
     *
501
     * @param string $cert the name of a file containing a PEM formatted certificate
502
     * @param string $certPass the password required to use it
503 697
     * @return $this
504
     */
505 66
    public function setCertificate($cert, $certPass = '')
506
    {
507 66
        $this->cert = $cert;
508 697
        $this->certpass = $certPass;
509 28
        return $this;
510 28
    }
511 28
512
    /**
513
     * Add a CA certificate to verify server with in SSL-enabled communication when SetSSLVerifypeer has been set to TRUE.
514
     *
515 697
     * See the php manual page about CURLOPT_CAINFO for more details.
516
     *
517
     * @param string $caCert certificate file name (or dir holding certificates)
518
     * @param bool $isDir set to true to indicate cacert is a dir. defaults to false
519 697
     * @return $this
520 697
     */
521
    public function setCaCertificate($caCert, $isDir = false)
522 697
    {
523 322
        if ($isDir) {
524 322
            $this->cacertdir = $caCert;
525 322
        } else {
526 322
            $this->cacert = $caCert;
527
        }
528 322
        return $this;
529 322
    }
530 322
531 322
    /**
532 322
     * Set attributes for SSL communication: private SSL key.
533 322
     *
534 322
     * NB: does not work in older php/curl installs.
535 322
     * Thanks to Daniel Convissor.
536 322
     *
537 322
     * @param string $key The name of a file containing a private SSL key
538 322
     * @param string $keyPass The secret password needed to use the private SSL key
539 322
     * @return $this
540
     */
541 322
    public function setKey($key, $keyPass)
542 322
    {
543 322
        $this->key = $key;
544 322
        $this->keypass = $keyPass;
545 322
        return $this;
546
    }
547
548
    /**
549 375
     * Set attributes for SSL communication: verify the remote host's SSL certificate, and cause the connection to fail
550 375
     * if the cert verification fails.
551 375
     *
552 375
     * By default, verification is enabled.
553
     * To specify custom SSL certificates to validate the server with, use the setCaCertificate method.
554 375
     *
555 375
     * @param bool $i enable/disable verification of peer certificate
556 375
     * @return $this
557 375
     * @deprecated use setOption
558 375
     */
559 375
    public function setSSLVerifyPeer($i)
560 375
    {
561 375
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
562 375
563 375
        $this->verifypeer = $i;
564 375
        return $this;
565 375
    }
566
567 375
    /**
568 375
     * Set attributes for SSL communication: verify the remote host's SSL certificate's common name (CN).
569 375
     *
570
     * Note that support for value 1 has been removed in cURL 7.28.1
571
     *
572
     * @param int $i Set to 1 to only the existence of a CN, not that it matches
573 697
     * @return $this
574
     * @deprecated use setOption
575
     */
576
    public function setSSLVerifyHost($i)
577
    {
578
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
579
580
        $this->verifyhost = $i;
581
        return $this;
582
    }
583
584
    /**
585
     * Set attributes for SSL communication: SSL version to use. Best left at 0 (default value): let PHP decide.
586
     *
587
     * @param int $i use CURL_SSLVERSION_ constants. When in socket mode, use values 2 (SSLv2) to 7 (TLSv1.3). 0 for auto
588
     * @return $this
589
     * @deprecated use setOption
590
     */
591
    public function setSSLVersion($i)
592
    {
593
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
594
595
        $this->sslversion = $i;
596
        return $this;
597
    }
598
599
    /**
600
     * Set proxy info.
601
     *
602
     * NB: CURL versions before 7.11.10 cannot use a proxy to communicate with https servers.
603
     *
604
     * @param string $proxyHost
605
     * @param string $proxyPort Defaults to 8080 for HTTP and 443 for HTTPS
606
     * @param string $proxyUsername Leave blank if proxy has public access
607
     * @param string $proxyPassword Leave blank if proxy has public access
608
     * @param int $proxyAuthType defaults to CURLAUTH_BASIC (Basic authentication protocol); set to constant CURLAUTH_NTLM
609
     *                           to use NTLM auth with proxy (has effect only when the client uses the HTTP 1.1 protocol)
610
     * @return $this
611
     */
612
    public function setProxy($proxyHost, $proxyPort, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1)
613
    {
614
        $this->proxy = $proxyHost;
615
        $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...
616
        $this->proxy_user = $proxyUsername;
617
        $this->proxy_pass = $proxyPassword;
618
        $this->proxy_authtype = $proxyAuthType;
619
        return $this;
620
    }
621
622
    /**
623
     * Enables/disables reception of compressed xml-rpc responses.
624
     *
625
     * This requires the "zlib" extension to be enabled in your php install. If it is, by default xmlrpc_client
626
     * instances will enable reception of compressed content.
627
     * Note that enabling reception of compressed responses merely adds some standard http headers to xml-rpc requests.
628
     * It is up to the xml-rpc server to return compressed responses when receiving such requests.
629
     *
630
     * @param string $compMethod either 'gzip', 'deflate', 'any' or ''
631
     * @return $this
632
     */
633
    public function setAcceptedCompression($compMethod)
634
    {
635
        if ($compMethod == 'any') {
636
            $this->accepted_compression = array('gzip', 'deflate');
637
        } 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...
638
            $this->accepted_compression = array();
639
        } else {
640
            $this->accepted_compression = array($compMethod);
641
        }
642
        return $this;
643
    }
644
645
    /**
646
     * Enables/disables http compression of xml-rpc request.
647
     *
648
     * This requires the "zlib" extension to be enabled in your php install.
649
     * Take care when sending compressed requests: servers might not support them (and automatic fallback to
650
     * uncompressed requests is not yet implemented).
651
     *
652
     * @param string $compMethod either 'gzip', 'deflate' or ''
653
     * @return $this
654
     * @deprecated use setOption
655
     */
656
    public function setRequestCompression($compMethod)
657
    {
658
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
659
660
        $this->request_compression = $compMethod;
661
        return $this;
662
    }
663
664 375
    /**
665
     * Adds a cookie to list of cookies that will be sent to server with every further request (useful e.g. for keeping
666
     * session info outside the xml-rpc payload).
667
     *
668
     * NB: by default all cookies set via this method are sent to the server, regardless of path/domain/port. Taking
669
     * advantage of those values is left to the single developer.
670
     *
671 375
     * @param string $name nb: will not be escaped in the request's http headers. Take care not to use CTL chars or
672 373
     *                     separators!
673
     * @param string $value
674
     * @param string $path
675
     * @param string $domain
676 375
     * @param int $port do not use! Cookies are not separated by port
677 346
     * @return $this
678
     *
679
     * @todo check correctness of urlencoding cookie value (copied from php way of doing it, but php is generally sending
680 375
     *       response not requests. We do the opposite...)
681
     * @todo strip invalid chars from cookie name? As per RFC 6265, we should follow RFC 2616, Section 2.2
682 375
     * @todo drop/rename $port parameter. Cookies are not isolated by port!
683 375
     * @todo feature-creep allow storing 'expires', 'secure', 'httponly' and 'samesite' cookie attributes (we could do
684 64
     *       as php, and allow $path to be an array of attributes...)
685 32
     */
686 32
    public function setCookie($name, $value = '', $path = '', $domain = '', $port = null)
687 32
    {
688 32
        $this->cookies[$name]['value'] = rawurlencode($value);
689
        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...
690
            $this->cookies[$name]['path'] = $path;
691 32
            $this->cookies[$name]['domain'] = $domain;
692 32
            $this->cookies[$name]['port'] = $port;
693 32
        }
694 32
        return $this;
695
    }
696
697
    /**
698
     * Directly set cURL options, for extra flexibility (when in cURL mode).
699
     *
700 375
     * It allows e.g. to bind client to a specific IP interface / address.
701 375
     *
702 32
     * @param array $options
703 32
     * @return $this
704
     * @deprecated use setOption
705
     */
706
    public function setCurlOptions($options)
707
    {
708 375
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
709 375
710 73
        $this->extracurlopts = $options;
711
        return $this;
712
    }
713 375
714 375
    /**
715 32
     * @param int $useCurlMode self::USE_CURL_ALWAYS, self::USE_CURL_AUTO or self::USE_CURL_NEVER
716
     * @return $this
717
     * @deprecated use setOption
718 32
     */
719 32
    public function setUseCurl($useCurlMode)
720 32
    {
721 32
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
722 32
723
        $this->use_curl = $useCurlMode;
724
        return $this;
725
    }
726 32
727
728
    /**
729 343
     * Set user-agent string that will be used by this client instance in http headers sent to the server.
730 343
     *
731 343
     * The default user agent string includes the name of this library and the version number.
732 343
     *
733
     * @param string $agentString
734
     * @return $this
735
     * @deprecated use setOption
736 375
     */
737 375
    public function setUserAgent($agentString)
738 309
    {
739 309
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
740 309
741
        $this->user_agent = $agentString;
742
        return $this;
743
    }
744
745
    /**
746
     * @param null|int $component allowed values: PHP_URL_SCHEME, PHP_URL_HOST, PHP_URL_PORT, PHP_URL_PATH
747
     * @return string|int Notes: the path component will include query string and fragment; NULL is a valid value for port
748
     *                    (in which case the default port for http/https will be used);
749
     * @throws ValueErrorException on unsupported component
750
     */
751
    public function getUrl($component = null)
752
    {
753 309
        if (is_int($component) || ctype_digit($component)) {
754
            switch ($component) {
755
                case PHP_URL_SCHEME:
756 309
                    return $this->method;
757
                case PHP_URL_HOST:
758
                    return $this->server;
759
                case PHP_URL_PORT:
760 375
                    return $this->port;
761 375
                case  PHP_URL_PATH:
762
                    return $this->path;
763
                case '':
764
765
                default:
766 375
                    throw new ValueErrorException("Unsupported component '$component'");
767 375
            }
768 375
        }
769 375
770 375
        $url = $this->method . '://' . $this->server;
771 375
        if ($this->port == 0 || ($this->port == 80 && in_array($this->method, array('http', 'http10', 'http11', 'h2c'))) ||
772 375
            ($this->port == 443 && in_array($this->method, array('https', 'h2')))) {
773 375
            return $url . $this->path;
774 375
        } else {
775 375
            return $url . ':' . $this->port . $this->path;
776 375
        }
777 375
    }
778
779 375
    /**
780 2
     * Send an xml-rpc request to the server.
781
     *
782
     * @param Request|Request[]|string $req The Request object, or an array of requests for using multicall, or the
783 375
     *                                      complete xml representation of a request.
784 375
     *                                      When sending an array of Request objects, the client will try to make use of
785 32
     *                                      a single 'system.multicall' xml-rpc method call to forward to the server all
786
     *                                      the requests in a single HTTP round trip, unless $this->no_multicall has
787
     *                                      been previously set to TRUE (see the multicall method below), in which case
788
     *                                      many consecutive xml-rpc requests will be sent. The method will return an
789
     *                                      array of Response objects in both cases.
790
     *                                      The third variant allows to build by hand (or any other means) a complete
791 32
     *                                      xml-rpc request message, and send it to the server. $req should be a string
792
     *                                      containing the complete xml representation of the request. It is e.g. useful
793
     *                                      when, for maximal speed of execution, the request is serialized into a
794 32
     *                                      string using the native php xml-rpc functions (see http://www.php.net/xmlrpc)
795
     * @param integer $timeout deprecated. Connection timeout, in seconds, If unspecified, the timeout set with setOption
796
     *                         will be used. If that is 0, a platform specific timeout will apply.
797 32
     *                         This timeout value is passed to fsockopen(). It is also used for detecting server
798
     *                         timeouts during communication (i.e. if the server does not send anything to the client
799
     *                         for $timeout seconds, the connection will be closed). When in CURL mode, this is the
800 32
     *                         CURL timeout.
801 32
     *                         NB: in both CURL and Socket modes, some conditions might lead to the client not
802
     *                        respecting the given timeout. Eg. if the network is not connected
803
     * @param string $method deprecated. Use the same value in the constructor instead.
804 375
     *                       Valid values are 'http', 'http11', 'https', 'h2' and 'h2c'. If left empty,
805
     *                       the http protocol chosen during creation of the object will be used.
806 375
     *                       Use 'h2' to make the lib attempt to use http/2 over a secure connection, and 'h2c'
807 64
     *                       for http/2 without tls. Note that 'h2c' will not use the h2c 'upgrade' method, and be
808
     *                       thus incompatible with any server/proxy not supporting http/2. This is because POST
809 311
     *                       request are not compatible with h2c upgrade.
810
     * @return Response|Response[] Note that the client will always return a Response object, even if the call fails
811
     *
812 375
     * @todo allow throwing exceptions instead of returning responses in case of failed calls and/or Fault responses
813 375
     * @todo refactor: we now support many options besides connection timeout and http version to use. Why only privilege those?
814
     */
815 375
    public function send($req, $timeout = 0, $method = '')
816 375
    {
817 375
        if ($method !== '' || $timeout !== 0) {
818 374
            $this->logDeprecation("Using non-default values for arguments 'method' and 'timeout' when calling method " . __METHOD__ . ' is deprecated');
819 367
        }
820
821
        // if user does not specify http protocol, use native method of this client
822 1
        // (i.e. method set during call to constructor)
823
        if ($method == '') {
824
            $method = $this->method;
825
        }
826
827 1
        if ($timeout == 0) {
828 1
            $timeout = $this->timeout;
829
        }
830 1
831
        if (is_array($req)) {
832
            // $req is an array of Requests
833 374
            /// @todo switch to the new syntax for multicall
834
            return $this->multicall($req, $timeout, $method);
835
        } elseif (is_string($req)) {
836
            $n = new static::$requestClass('');
837
            /// @todo we should somehow allow the caller to declare a custom contenttype too, esp. for the charset declaration
838
            $n->setPayload($req);
839
            $req = $n;
840
        }
841
842
        // where req is a Request
843 374
        $req->setDebug($this->debug);
844
845
        /// @todo we could be smarter about this and not force usage of curl for https if not present as well as use the
846
        ///       presence of curl_extra_opts or socket_extra_opts as a hint
847 374
        $useCurl = ($this->use_curl == self::USE_CURL_ALWAYS) || ($this->use_curl == self::USE_CURL_AUTO && (
848 374
            in_array($method, array('https', 'http11', 'h2c', 'h2')) ||
849 374
            ($this->username != '' && $this->authtype != 1) ||
850
            ($this->proxy != '' && $this->proxy_user != '' && $this->proxy_authtype != 1)
851 374
        ));
852
853 374
        // BC - we go through sendPayloadCURL/sendPayloadSocket in case some subclass reimplemented those
854
        if ($useCurl) {
855
            $r = $this->sendPayloadCURL(
0 ignored issues
show
Deprecated Code introduced by
The function PhpXmlRpc\Client::sendPayloadCURL() has been deprecated. ( Ignorable by Annotation )

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

855
            $r = /** @scrutinizer ignore-deprecated */ $this->sendPayloadCURL(
Loading history...
856
                $req,
857
                $this->server,
858
                $this->port,
859
                $timeout,
860
                $this->username,
861
                $this->password,
862
                $this->authtype,
863
                $this->cert,
864
                $this->certpass,
865
                $this->cacert,
866
                $this->cacertdir,
867
                $this->proxy,
868
                $this->proxyport,
869
                $this->proxy_user,
870
                $this->proxy_pass,
871
                $this->proxy_authtype,
872
                // BC
873
                $method == 'http11' ? 'http' : $method,
874
                $this->keepalive,
875
                $this->key,
876
                $this->keypass,
877
                $this->sslversion
878
            );
879
        } else {
880
            $r = $this->sendPayloadSocket(
0 ignored issues
show
Deprecated Code introduced by
The function PhpXmlRpc\Client::sendPayloadSocket() has been deprecated. ( Ignorable by Annotation )

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

880
            $r = /** @scrutinizer ignore-deprecated */ $this->sendPayloadSocket(
Loading history...
881
                $req,
882
                $this->server,
883
                $this->port,
884
                $timeout,
885
                $this->username,
886 322
                $this->password,
887
                $this->authtype,
888
                $this->cert,
889
                $this->certpass,
890
                $this->cacert,
891 322
                $this->cacertdir,
892
                $this->proxy,
893
                $this->proxyport,
894
                $this->proxy_user,
895 322
                $this->proxy_pass,
896
                $this->proxy_authtype,
897 96
                $method,
898 96
                $this->key,
899
                $this->keypass,
900
                $this->sslversion
901
            );
902
        }
903
904 322
        return $r;
905 322
    }
906
907
    /**
908
     * @param Request $req
909
     * @param string $method
910 322
     * @param string $server
911
     * @param int $port
912
     * @param string $path
913
     * @param array $opts
914
     * @return Response
915 322
     */
916
    protected function sendViaSocket($req, $method, $server, $port, $path, $opts)
917 322
    {
918
        /// @todo log a warning if passed an unsupported method
919
920
        // Only create the payload if it was not created previously
921
        /// @todo what if the request's payload was created with a different encoding?
922
        ///       Also, if we do not call serialize(), the request will not set its content-type to have the charset declared
923
        $payload = $req->getPayload();
924
        if (empty($payload)) {
925
            $payload = $req->serialize($opts['request_charset_encoding']);
926
        }
927
928
        // Deflate request body and set appropriate request headers
929 322
        $encodingHdr = '';
930
        if ($opts['request_compression'] == 'gzip' || $opts['request_compression'] == 'deflate') {
931
            if ($opts['request_compression'] == 'gzip' && function_exists('gzencode')) {
932 1
                $a = @gzencode($payload);
933 1
                if ($a) {
934 1
                    $payload = $a;
935 1
                    $encodingHdr = "Content-Encoding: gzip\r\n";
936 1
                } else {
937
                    $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzencode failure in compressing request');
938
                }
939 322
            } else if (function_exists('gzcompress')) {
940 160
                $a = @gzcompress($payload);
941
                if ($a) {
942 322
                    $payload = $a;
943
                    $encodingHdr = "Content-Encoding: deflate\r\n";
944 322
                } else {
945
                    $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzcompress failure in compressing request');
946
                }
947
            } else {
948
                $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported by this PHP install');
949
            }
950 322
        } else {
951
            if ($opts['request_compression'] != '') {
952
                $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported');
953 323
            }
954
        }
955
956
        // thanks to Grant Rauscher
957
        $credentials = '';
958 323
        if ($opts['username'] != '') {
959 322
            if ($opts['authtype'] != 1) {
960 226
                $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported with HTTP 1.0');
961
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
962 96
                    PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': only Basic auth is supported with HTTP 1.0');
963
            }
964
            $credentials = 'Authorization: Basic ' . base64_encode($opts['username'] . ':' . $opts['password']) . "\r\n";
965
        }
966
967 323
        $acceptedEncoding = '';
968 302
        if (is_array($opts['accepted_compression']) && count($opts['accepted_compression'])) {
969
            $acceptedEncoding = 'Accept-Encoding: ' . implode(', ', $opts['accepted_compression']) . "\r\n";
970
        }
971
972 323
        if ($port == 0) {
973 323
            $port = ($method === 'https') ? 443 : 80;
974 64
        }
975 32
976 32
        $proxyCredentials = '';
977 32
        if ($opts['proxy']) {
978 32
            if ($opts['proxyport'] == 0) {
979
                $opts['proxyport'] = 8080;
980
            }
981 32
            $connectServer = $opts['proxy'];
982 32
            $connectPort = $opts['proxyport'];
983 32
            $transport = 'tcp';
984 64
            /// @todo check: should we not use https in some cases?
985
            $uri = 'http://' . $server . ':' . $port . $path;
986
            if ($opts['proxy_user'] != '') {
987
                if ($opts['proxy_authtype'] != 1) {
988 259
                    $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported with HTTP 1.0');
989
                    return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
990
                        PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': only Basic auth to proxy is supported with HTTP 1.0');
991 323
                }
992 323
                $proxyCredentials = 'Proxy-Authorization: Basic ' . base64_encode($opts['proxy_user'] . ':' .
993 64
                    $opts['proxy_pass']) . "\r\n";
994
            }
995 259
        } else {
996 32
            $connectServer = $server;
997
            $connectPort = $port;
998
            $transport = ($method === 'https') ? 'tls' : 'tcp';
999 227
            $uri = $path;
1000
        }
1001
1002 323
        // Cookie generation, as per RFC 6265
1003 323
        // NB: the following code does not honour 'expires', 'path' and 'domain' cookie attributes set to client obj by the user...
1004 322
        $cookieHeader = '';
1005
        if (count($opts['cookies'])) {
1006
            $version = '';
1007 56
            foreach ($opts['cookies'] as $name => $cookie) {
1008
                /// @todo should we sanitize the cookie value on behalf of the user? See setCookie comments
1009
                $cookieHeader .= ' ' . $name . '=' . $cookie['value'] . ";";
1010
            }
1011 323
            $cookieHeader = 'Cookie:' . $version . substr($cookieHeader, 0, -1) . "\r\n";
1012
        }
1013 323
1014
        $extraHeaders = '';
1015
        if (is_array($this->extra_headers) && $this->extra_headers) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->extra_headers of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1016
            $extraHeaders = implode("\r\n", $this->extra_headers) . "\r\n";
1017 323
        }
1018
1019 323
        // omit port if default
1020
        if (($port == 80 && in_array($method, array('http', 'http10'))) || ($port == 443 && $method == 'https')) {
1021 323
            $port = '';
1022
        } else {
1023
            $port = ':' . $port;
1024 323
        }
1025
1026
        $op = 'POST ' . $uri . " HTTP/1.0\r\n" .
1027
            'User-Agent: ' . $opts['user_agent'] . "\r\n" .
1028 323
            'Host: ' . $server . $port . "\r\n" .
1029
            $credentials .
1030
            $proxyCredentials .
1031 66
            $acceptedEncoding .
1032 64
            $encodingHdr .
1033
            'Accept-Charset: ' . implode(',', $opts['accepted_charset_encodings']) . "\r\n" .
1034 2
            $cookieHeader .
1035
            'Content-Type: ' . $req->getContentType() . "\r\n" .
1036
            $extraHeaders .
1037
            'Content-Length: ' . strlen($payload) . "\r\n\r\n" .
1038 323
            $payload;
1039
1040 323
        if ($opts['debug'] > 1) {
1041 161
            $this->getLogger()->debug("---SENDING---\n$op\n---END---");
1042
        }
1043
1044 323
        $contextOptions = array();
1045 64
        if ($method == 'https') {
1046
            if ($opts['cert'] != '') {
1047
                $contextOptions['ssl']['local_cert'] = $opts['cert'];
1048
                if ($opts['certpass'] != '') {
1049
                    $contextOptions['ssl']['passphrase'] = $opts['certpass'];
1050 323
                }
1051
            }
1052 323
            if ($opts['cacert'] != '') {
1053
                $contextOptions['ssl']['cafile'] = $opts['cacert'];
1054 323
            }
1055 272
            if ($opts['cacertdir'] != '') {
1056
                $contextOptions['ssl']['capath'] = $opts['cacertdir'];
1057
            }
1058 323
            if ($opts['key'] != '') {
1059 323
                $contextOptions['ssl']['local_pk'] = $opts['key'];
1060 32
            }
1061 32
            $contextOptions['ssl']['verify_peer'] = $opts['verifypeer'];
1062 291
            $contextOptions['ssl']['verify_peer_name'] = $opts['verifypeer'];
1063
1064
            if ($opts['sslversion'] != 0) {
1065 291
                /// @see https://www.php.net/manual/en/function.curl-setopt.php, https://www.php.net/manual/en/migration56.openssl.php
1066 32
                switch($opts['sslversion']) {
1067 32
                    /// @todo what does this map to? 1.0-1.3?
1068 259
                    //case 1: // TLSv1
1069 32
                    //    break;
1070 32
                    case 2: // SSLv2
1071
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_SSLv2_CLIENT;
1072
                        break;
1073 323
                    case 3: // SSLv3
1074 32
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_SSLv3_CLIENT;
1075 32
                        break;
1076 32
                    case 4: // TLSv1.0
1077
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT;
1078
                        break;
1079
                    case 5: // TLSv1.1
1080
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
1081
                        break;
1082 323
                    case 6: // TLSv1.2
1083
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
1084 96
                        break;
1085
                    case 7: // TLSv1.3
1086
                        if (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT')) {
1087
                            $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT;
1088 96
                        } else {
1089
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
1090
                                PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': TLS-1.3 only is supported with PHP 7.4 or later');
1091
                        }
1092 96
                        break;
1093
                    default:
1094 96
                        return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
1095
                            PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': Unsupported required TLS version');
1096
                }
1097 96
            }
1098
        }
1099
1100
        foreach ($opts['extrasockopts'] as $proto => $protoOpts) {
1101 96
            foreach ($protoOpts as $key => $val) {
1102
                $contextOptions[$proto][$key] = $val;
1103
            }
1104
        }
1105 96
1106
        $context = stream_context_create($contextOptions);
1107
1108
        if ($opts['timeout'] <= 0) {
1109
            $connectTimeout = ini_get('default_socket_timeout');
1110 96
        } else {
1111
            $connectTimeout = $opts['timeout'];
1112 96
        }
1113
1114
        $this->errno = 0;
1115
        $this->errstr = '';
1116 323
1117 64
        $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr, $connectTimeout,
1118
            STREAM_CLIENT_CONNECT, $context);
1119
        if ($fp) {
0 ignored issues
show
introduced by
$fp is of type false|resource, thus it always evaluated to false.
Loading history...
1120 64
            if ($opts['timeout'] > 0) {
1121 64
                stream_set_timeout($fp, $opts['timeout'], 0);
1122
            }
1123
        } else {
1124
            if ($this->errstr == '') {
1125
                $err = error_get_last();
1126
                $this->errstr = $err['message'];
1127
            }
1128
1129
            $this->errstr = 'Connect error: ' . $this->errstr;
1130
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr . ' (' . $this->errno . ')');
1131
        }
1132
1133 323
        if (!fputs($fp, $op, strlen($op))) {
1134 271
            fclose($fp);
1135 271
            $this->errstr = 'Write error';
1136 271
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr);
1137
        }
1138 271
1139
        $info = stream_get_meta_data($fp);
1140
        if ($info['timed_out']) {
1141 323
            fclose($fp);
1142
            $this->errstr = 'Write timeout';
1143
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr);
1144
        }
1145 323
1146
        // Close socket before parsing.
1147
        // It should yield slightly better execution times, and make easier recursive calls (e.g. to follow http redirects)
1148
        $ipd = '';
1149 323
        do {
1150
            // shall we check for $data === FALSE?
1151
            // as per the manual, it signals an error
1152
            $ipd .= fread($fp, 32768);
1153
1154
            $info = stream_get_meta_data($fp);
1155
            if ($info['timed_out']) {
1156
                fclose($fp);
1157
                $this->errstr = 'Read timeout';
1158
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr);
1159
            }
1160
1161
        } while (!feof($fp));
1162
        fclose($fp);
1163
1164
        return $req->parseResponse($ipd, false, $opts['return_type']);
1165
    }
1166
1167
    /**
1168
     * Contributed by Justin Miller
1169
     * Requires curl to be built into PHP
1170
     * NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers!
1171
     *
1172
     * @param Request $req
1173
     * @param string $method
1174 66
     * @param string $server
1175
     * @param int $port
1176 66
     * @param string $path
1177
     * @param array $opts the keys/values match self::getOptions
1178
     * @return Response
1179 66
     *
1180 45
     * @todo the $path arg atm is ignored. What to do if it is != $this->path?
1181 45
     */
1182
    protected function sendViaCURL($req, $method, $server, $port, $path, $opts)
0 ignored issues
show
Unused Code introduced by
The parameter $path 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

1182
    protected function sendViaCURL($req, $method, $server, $port, /** @scrutinizer ignore-unused */ $path, $opts)

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...
1183 45
    {
1184
        if (!function_exists('curl_init')) {
1185
            $this->errstr = 'CURL unavailable on this install';
1186
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_curl'], PhpXmlRpc::$xmlrpcstr['no_curl']);
1187
        }
1188
        if ($method == 'https' || $method == 'h2') {
1189
            // q: what about installs where we get back a string, but curl is linked to other ssl libs than openssl?
1190
            if (($info = curl_version()) &&
1191
                ((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...
1192
            ) {
1193
                $this->errstr = 'SSL unavailable on this install';
1194
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_ssl'], PhpXmlRpc::$xmlrpcstr['no_ssl']);
1195
            }
1196
        }
1197
        if (($method == 'h2' && !defined('CURL_HTTP_VERSION_2_0')) ||
1198
            ($method == 'h2c' && !defined('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE'))) {
1199
            $this->errstr = 'HTTP/2 unavailable on this install';
1200
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_http2'], PhpXmlRpc::$xmlrpcstr['no_http2']);
1201 23
        }
1202
1203
        // BC - we go through prepareCurlHandle in case some subclass reimplemented it
1204 23
        $curl = $this->prepareCurlHandle($req, $server, $port, $opts['timeout'], $opts['username'], $opts['password'],
0 ignored issues
show
Deprecated Code introduced by
The function PhpXmlRpc\Client::prepareCurlHandle() has been deprecated. ( Ignorable by Annotation )

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

1204
        $curl = /** @scrutinizer ignore-deprecated */ $this->prepareCurlHandle($req, $server, $port, $opts['timeout'], $opts['username'], $opts['password'],
Loading history...
1205 23
            $opts['authtype'], $opts['cert'], $opts['certpass'], $opts['cacert'], $opts['cacertdir'], $opts['proxy'],
1206
            $opts['proxyport'], $opts['proxy_user'], $opts['proxy_pass'], $opts['proxy_authtype'], $method,
1207
            $opts['keepalive'], $opts['key'], $opts['keypass'], $opts['sslversion']);
1208
1209 23
        if (!$curl) {
1210 23
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] .
1211
                ': error during curl initialization. Check php error log for details');
1212
        }
1213
1214
        $result = curl_exec($curl);
1215
1216
        if ($opts['debug'] > 1) {
1217
            $message = "---CURL INFO---\n";
1218
            foreach (curl_getinfo($curl) as $name => $val) {
1219
                if (is_array($val)) {
1220
                    $val = implode("\n", $val);
1221 23
                }
1222
                $message .= $name . ': ' . $val . "\n";
1223
            }
1224
            $message .= '---END---';
1225
            $this->getLogger()->debug($message);
1226
        }
1227
1228
        if (!$result) {
1229
            /// @todo we should use a better check here - what if we get back '' or '0'?
1230
1231
            $this->errstr = 'no response';
1232
            $resp = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] .
1233
                ': ' . curl_error($curl));
1234
            curl_close($curl);
1235 45
            if ($opts['keepalive']) {
1236
                $this->xmlrpc_curl_handle = null;
1237
            }
1238 45
        } else {
1239 45
            if (!$opts['keepalive']) {
1240 45
                curl_close($curl);
1241 45
            }
1242 45
            $resp = $req->parseResponse($result, true, $opts['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

1242
            $resp = $req->parseResponse(/** @scrutinizer ignore-type */ $result, true, $opts['return_type']);
Loading history...
1243 45
            if ($opts['keepalive']) {
1244 45
                /// @todo if we got back a 302 or 308, we should not reuse the curl handle for later calls
1245
                if ($resp->faultCode() == PhpXmlRpc::$xmlrpcerr['http_error']) {
1246 45
                    curl_close($curl);
1247 45
                    $this->xmlrpc_curl_handle = null;
1248
                }
1249 45
            }
1250 45
        }
1251
1252
        return $resp;
1253 45
    }
1254
1255 45
    /**
1256
     * @param Request $req
1257
     * @param string $method
1258
     * @param string $server
1259
     * @param int $port
1260
     * @param string $path
1261 45
     * @param array $opts the keys/values match self::getOptions
1262
     * @return \CurlHandle|resource|false
1263 45
     *
1264
     * @todo allow this method to either throw or return a Response, so that we can pass back to caller more info on errors
1265 45
     */
1266
    protected function createCURLHandle($req, $method, $server, $port, $path, $opts)
1267 22
    {
1268 22
        if ($port == 0) {
1269
            if (in_array($method, array('http', 'http10', 'http11', 'h2c'))) {
1270
                $port = 80;
1271 22
            } else {
1272 22
                $port = 443;
1273
            }
1274
        }
1275
1276 22
        // Only create the payload if it was not created previously
1277 22
        $payload = $req->getPayload();
1278 22
        if (empty($payload)) {
1279 22
            $payload = $req->serialize($opts['request_charset_encoding']);
1280
        }
1281
1282 22
        // Deflate request body and set appropriate request headers
1283 22
        $encodingHdr = '';
1284 22
        if (($opts['request_compression'] == 'gzip' || $opts['request_compression'] == 'deflate')) {
1285
            if ($opts['request_compression'] == 'gzip' && function_exists('gzencode')) {
1286
                $a = @gzencode($payload);
1287
                if ($a) {
1288 22
                    $payload = $a;
1289 22
                    $encodingHdr = 'Content-Encoding: gzip';
1290 22
                } else {
1291
                    $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzencode failure in compressing request');
1292 22
                }
1293 22
            } else if (function_exists('gzcompress')) {
1294
                $a = @gzcompress($payload);
1295
                if ($a) {
1296 22
                    $payload = $a;
1297 22
                    $encodingHdr = 'Content-Encoding: deflate';
1298
                } else {
1299
                    $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzcompress failure in compressing request');
1300 22
                }
1301 22
            } else {
1302
                $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported by this PHP install');
1303
            }
1304
        } else {
1305
            if ($opts['request_compression'] != '') {
1306
                $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported');
1307 22
            }
1308
        }
1309
1310
        if (!$opts['keepalive'] || !$this->xmlrpc_curl_handle) {
1311 24
            if ($method == 'http11' || $method == 'http10' || $method == 'h2c') {
1312 24
                $protocol = 'http';
1313
            } else {
1314
                if ($method == 'h2') {
1315 24
                    $protocol = 'https';
1316 24
                } else {
1317
                    // http, https
1318
                    $protocol = $method;
1319
                    if (strpos($protocol, ':') !== false) {
1320 24
                        $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ": warning - attempted hacking attempt?. The curl protocol requested for the call is: '$protocol'");
1321 24
                        return false;
1322 24
                    }
1323 24
                }
1324 24
            }
1325
            $curl = curl_init($protocol . '://' . $server . ':' . $port . $path);
1326
            if (!$curl) {
1327
                return false;
1328 24
            }
1329 24
            if ($opts['keepalive']) {
1330 22
                $this->xmlrpc_curl_handle = $curl;
1331 22
            }
1332
        } else {
1333 22
            $curl = $this->xmlrpc_curl_handle;
1334
        }
1335
1336 22
        // results into variable
1337
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
1338 22
1339
        if ($opts['debug'] > 1) {
1340
            curl_setopt($curl, CURLOPT_VERBOSE, true);
1341 22
            /// @todo redirect curlopt_stderr to some stream which can be piped to the logger
1342 22
        }
1343
        curl_setopt($curl, CURLOPT_USERAGENT, $opts['user_agent']);
1344
        // required for XMLRPC: post the data
1345
        curl_setopt($curl, CURLOPT_POST, 1);
1346
        // the data
1347
        curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
1348 24
1349
        // return the header too
1350
        curl_setopt($curl, CURLOPT_HEADER, 1);
1351
1352
        // NB: if we set an empty string, CURL will add http header indicating
1353
        // ALL methods it is supporting. This is possibly a better option than letting the user tell what curl can / cannot do...
1354
        if (is_array($opts['accepted_compression']) && count($opts['accepted_compression'])) {
1355
            //curl_setopt($curl, CURLOPT_ENCODING, implode(',', $opts['accepted_compression']));
1356
            // empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1357
            if (count($opts['accepted_compression']) == 1) {
1358
                curl_setopt($curl, CURLOPT_ENCODING, $opts['accepted_compression'][0]);
1359
            } else {
1360
                curl_setopt($curl, CURLOPT_ENCODING, '');
1361
            }
1362
        }
1363
        // extra headers
1364
        $headers = array('Content-Type: ' . $req->getContentType(), 'Accept-Charset: ' . implode(',', $opts['accepted_charset_encodings']));
1365
        // if no keepalive is wanted, let the server know it in advance
1366
        if (!$opts['keepalive']) {
1367
            $headers[] = 'Connection: close';
1368
        }
1369
        // request compression header
1370
        if ($encodingHdr) {
1371
            $headers[] = $encodingHdr;
1372
        }
1373
1374
        if (is_array($this->extra_headers) && $this->extra_headers) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->extra_headers of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1375
            $headers = array_merge($headers, $this->extra_headers);
1376
        }
1377
1378
        // Fix the HTTP/1.1 417 Expectation Failed Bug (curl by default adds a 'Expect: 100-continue' header when POST
1379
        // size exceeds 1025 bytes, apparently)
1380
        $headers[] = 'Expect:';
1381
1382
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
1383
        // previous note: "timeout is borked" (on some old php/curl versions? It seems to work on 8.1. Maybe the issue
1384
        // has to do with dns resolution...)
1385
        if ($opts['timeout']) {
1386
            curl_setopt($curl, CURLOPT_TIMEOUT, $opts['timeout']);
1387
        }
1388
1389
        switch ($method) {
1390
            case 'http10':
1391
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
1392
                break;
1393
            case 'http11':
1394
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
1395
                break;
1396
            case 'h2c':
1397
                if (defined('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE')) {
1398
                    curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE);
1399
                } else {
1400
                    $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. HTTP2 is not supported by the current PHP/curl install');
1401
                    curl_close($curl);
1402
                    return false;
1403
                }
1404
                break;
1405
            case 'h2':
1406
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
1407
                break;
1408
        }
1409
1410
        if ($opts['username'] && $opts['password']) {
1411
            curl_setopt($curl, CURLOPT_USERPWD, $opts['username'] . ':' . $opts['password']);
1412
            if (defined('CURLOPT_HTTPAUTH')) {
1413
                curl_setopt($curl, CURLOPT_HTTPAUTH, $opts['authtype']);
1414
            } elseif ($opts['authtype'] != 1) {
1415
                $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported by the current PHP/curl install');
1416
                curl_close($curl);
1417
                return false;
1418
            }
1419
        }
1420
1421
        // note: h2c is http2 without the https. No need to have it in this IF
1422
        if ($method == 'https' || $method == 'h2') {
1423
            // set cert file
1424
            if ($opts['cert']) {
1425
                curl_setopt($curl, CURLOPT_SSLCERT, $opts['cert']);
1426
            }
1427
            // set cert password
1428
            if ($opts['certpass']) {
1429
                curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $opts['certpass']);
1430
            }
1431
            // whether to verify remote host's cert
1432
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $opts['verifypeer']);
1433
            // set ca certificates file/dir
1434
            if ($opts['cacert']) {
1435
                curl_setopt($curl, CURLOPT_CAINFO, $opts['cacert']);
1436
            }
1437
            if ($opts['cacertdir']) {
1438
                curl_setopt($curl, CURLOPT_CAPATH, $opts['cacertdir']);
1439
            }
1440
            // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1441
            if ($opts['key']) {
1442
                curl_setopt($curl, CURLOPT_SSLKEY, $opts['key']);
1443
            }
1444
            // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1445
            if ($opts['keypass']) {
1446
                curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $opts['keypass']);
1447
            }
1448
            // whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that
1449
            // it matches the hostname used
1450
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $opts['verifyhost']);
1451
            // allow usage of different SSL versions
1452
            curl_setopt($curl, CURLOPT_SSLVERSION, $opts['sslversion']);
1453
        }
1454
1455
        // proxy info
1456
        if ($opts['proxy']) {
1457
            if ($opts['proxyport'] == 0) {
1458
                $opts['proxyport'] = 8080; // NB: even for HTTPS, local connection is on port 8080
1459
            }
1460
            curl_setopt($curl, CURLOPT_PROXY, $opts['proxy'] . ':' . $opts['proxyport']);
1461
            if ($opts['proxy_user']) {
1462
                curl_setopt($curl, CURLOPT_PROXYUSERPWD, $opts['proxy_user'] . ':' . $opts['proxy_pass']);
1463
                if (defined('CURLOPT_PROXYAUTH')) {
1464
                    curl_setopt($curl, CURLOPT_PROXYAUTH, $opts['proxy_authtype']);
1465
                } elseif ($opts['proxy_authtype'] != 1) {
1466
                    $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported by the current PHP/curl install');
1467
                    curl_close($curl);
1468
                    return false;
1469
                }
1470
            }
1471
        }
1472
1473
        // NB: should we build cookie http headers by hand rather than let CURL do it?
1474
        // NB: the following code does not honour 'expires', 'path' and 'domain' cookie attributes set to client obj by the user...
1475
        if (count($opts['cookies'])) {
1476
            $cookieHeader = '';
1477
            foreach ($opts['cookies'] as $name => $cookie) {
1478
                $cookieHeader .= $name . '=' . $cookie['value'] . '; ';
1479
            }
1480
            curl_setopt($curl, CURLOPT_COOKIE, substr($cookieHeader, 0, -2));
1481
        }
1482
1483
        foreach ($opts['extracurlopts'] as $opt => $val) {
1484
            curl_setopt($curl, $opt, $val);
1485
        }
1486
1487
        if ($opts['debug'] > 1) {
1488
            $this->getLogger()->debug("---SENDING---\n$payload\n---END---");
1489
        }
1490
1491
        return $curl;
1492
    }
1493
1494
    /**
1495
     * Send an array of requests and return an array of responses.
1496
     *
1497
     * Unless $this->no_multicall has been set to true, it will try first to use one single xml-rpc call to server method
1498
     * system.multicall, and revert to sending many successive calls in case of failure.
1499
     * This failure is also stored in $this->no_multicall for subsequent calls.
1500
     * Unfortunately, there is no server error code universally used to denote the fact that multicall is unsupported,
1501
     * so there is no way to reliably distinguish between that and a temporary failure.
1502
     * If you are sure that server supports multicall and do not want to fallback to using many single calls, set the
1503
     * 2np parameter to FALSE.
1504
     *
1505
     * NB: trying to shoehorn extra functionality into existing syntax has resulted
1506
     * in pretty much convoluted code...
1507
     *
1508
     * @param Request[] $reqs an array of Request objects
1509
     * @param bool $noFallback When true, upon receiving an error during multicall, multiple single calls will not be
1510
     *                         attempted.
1511
     *                         Deprecated alternative, was: int - "connection timeout (in seconds). See the details in the
1512
     *                         docs for the send() method". Please use setOption instead to set a timeout
1513
     * @param string $method deprecated. Was: "the http protocol variant to be used. See the details in the docs for the send() method."
1514
     *                       Please use the constructor to set an http protocol variant.
1515
     * @param boolean $fallback deprecated. Was: "when true, upon receiving an error during multicall, multiple single
1516
     *                          calls will be attempted"
1517
     * @return Response[]
1518
     */
1519
    public function multicall($reqs, $timeout = 0, $method = '', $fallback = true)
1520
    {
1521
        // BC
1522
        if (is_bool($timeout) && $fallback === true) {
1523
            $fallback = !$timeout;
1524
            $timeout = 0;
1525
        }
1526
1527
        if ($method == '') {
1528
            $method = $this->method;
1529
        }
1530
1531
        if (!$this->no_multicall) {
1532
            $results = $this->_try_multicall($reqs, $timeout, $method);
1533
            /// @todo how to handle the case of $this->return_type = xml?
1534
            if (is_array($results)) {
1535
                // System.multicall succeeded
1536
                return $results;
1537
            } else {
1538
                // either system.multicall is unsupported by server, or the call failed for some other reason.
1539
                // Feature creep: is there a way to tell apart unsupported multicall from other faults?
1540
                if ($fallback) {
1541
                    // Don't try it next time...
1542
                    $this->no_multicall = true;
1543
                } else {
1544
                    $result = $results;
1545
                }
1546
            }
1547
        } else {
1548
            // override fallback, in case careless user tries to do two
1549
            // opposite things at the same time
1550
            $fallback = true;
1551
        }
1552
1553
        $results = array();
1554
        if ($fallback) {
1555
            // system.multicall is (probably) unsupported by server: emulate multicall via multiple requests
1556
            /// @todo use curl multi_ functions to make this quicker (see the implementation in the parallel.php demo)
1557
            foreach ($reqs as $req) {
1558
                $results[] = $this->send($req, $timeout, $method);
1559
            }
1560
        } else {
1561
            // user does NOT want to fallback on many single calls: since we should always return an array of responses,
1562
            // we return an array with the same error repeated n times
1563
            foreach ($reqs as $req) {
1564
                $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...
1565
            }
1566
        }
1567
1568
        return $results;
1569
    }
1570
1571
    /**
1572
     * Attempt to boxcar $reqs via system.multicall.
1573
     *
1574
     * @param Request[] $reqs
1575
     * @param int $timeout
1576
     * @param string $method
1577
     * @return Response[]|Response a single Response when the call returned a fault / does not conform to what we expect
1578
     *                             from a multicall response
1579
     */
1580
    private function _try_multicall($reqs, $timeout, $method)
1581
    {
1582
        // Construct multicall request
1583
        $calls = array();
1584
        foreach ($reqs as $req) {
1585
            $call['methodName'] = new Value($req->method(), 'string');
1586
            $numParams = $req->getNumParams();
1587
            $params = array();
1588
            for ($i = 0; $i < $numParams; $i++) {
1589
                $params[$i] = $req->getParam($i);
1590
            }
1591
            $call['params'] = new Value($params, 'array');
1592
            $calls[] = new Value($call, 'struct');
1593
        }
1594
        $multiCall = new static::$requestClass('system.multicall');
1595
        $multiCall->addParam(new Value($calls, 'array'));
1596
1597
        // Attempt RPC call
1598
        $result = $this->send($multiCall, $timeout, $method);
1599
1600
        if ($result->faultCode() != 0) {
1601
            // call to system.multicall failed
1602
            return $result;
1603
        }
1604
1605
        // Unpack responses.
1606
        $rets = $result->value();
1607
        $response = array();
1608
1609
        if ($this->return_type == 'xml') {
1610
            for ($i = 0; $i < count($reqs); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
1611
                $response[] = new static::$responseClass($rets, 0, '', 'xml', $result->httpResponse());
1612
            }
1613
1614
        } elseif ($this->return_type == 'phpvals') {
1615
            if (!is_array($rets)) {
1616
                // bad return type from system.multicall
1617
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1618
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': not an array', 'phpvals', $result->httpResponse());
1619
            }
1620
            $numRets = count($rets);
1621
            if ($numRets != count($reqs)) {
1622
                // wrong number of return values.
1623
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1624
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': incorrect number of responses', 'phpvals',
1625
                    $result->httpResponse());
1626
            }
1627
1628
            for ($i = 0; $i < $numRets; $i++) {
1629
                $val = $rets[$i];
1630
                if (!is_array($val)) {
1631
                    return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1632
                        PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array or struct",
1633
                        'phpvals', $result->httpResponse());
1634
                }
1635
                switch (count($val)) {
1636
                    case 1:
1637
                        if (!isset($val[0])) {
1638
                            // Bad value
1639
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1640
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has no value",
1641
                                'phpvals', $result->httpResponse());
1642
                        }
1643
                        // Normal return value
1644
                        $response[$i] = new static::$responseClass($val[0], 0, '', 'phpvals', $result->httpResponse());
1645
                        break;
1646
                    case 2:
1647
                        /// @todo remove usage of @: it is apparently quite slow
1648
                        $code = @$val['faultCode'];
1649
                        if (!is_int($code)) {
1650
                            /// @todo should we check that it is != 0?
1651
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1652
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1653
                                'phpvals', $result->httpResponse());
1654
                        }
1655
                        $str = @$val['faultString'];
1656
                        if (!is_string($str)) {
1657
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1658
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no FaultString",
1659
                                'phpvals', $result->httpResponse());
1660
                        }
1661
                        $response[$i] = new static::$responseClass(0, $code, $str, 'phpvals', $result->httpResponse());
1662
                        break;
1663
                    default:
1664
                        return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1665
                            PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1666
                            'phpvals', $result->httpResponse());
1667
                }
1668
            }
1669
1670
        } else {
1671
            // return type == 'xmlrpcvals'
1672
            if ($rets->kindOf() != 'array') {
1673
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1674
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array", 'xmlrpcvals',
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $i seems to be defined by a foreach iteration on line 1584. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
1675
                    $result->httpResponse());
1676
            }
1677
            $numRets = $rets->count();
1678
            if ($numRets != count($reqs)) {
1679
                // wrong number of return values.
1680
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1681
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': incorrect number of responses', 'xmlrpcvals',
1682
                    $result->httpResponse());
1683
            }
1684
1685
            foreach ($rets as $i => $val) {
1686
                switch ($val->kindOf()) {
1687
                    case 'array':
1688
                        if ($val->count() != 1) {
1689
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1690
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1691
                                'phpvals', $result->httpResponse());
1692
                        }
1693
                        // Normal return value
1694
                        $response[] = new static::$responseClass($val[0], 0, '', 'xmlrpcvals', $result->httpResponse());
1695
                        break;
1696
                    case 'struct':
1697
                        if ($val->count() != 2) {
1698
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1699
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1700
                                'phpvals', $result->httpResponse());
1701
                        }
1702
                        /** @var Value $code */
1703
                        $code = $val['faultCode'];
1704
                        if ($code->kindOf() != 'scalar' || $code->scalarTyp() != 'int') {
1705
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1706
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1707
                                'xmlrpcvals', $result->httpResponse());
1708
                        }
1709
                        /** @var Value $str */
1710
                        $str = $val['faultString'];
1711
                        if ($str->kindOf() != 'scalar' || $str->scalarTyp() != 'string') {
1712
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1713
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1714
                                'xmlrpcvals', $result->httpResponse());
1715
                        }
1716
                        $response[] = new static::$responseClass(0, $code->scalarVal(), $str->scalarVal(), 'xmlrpcvals', $result->httpResponse());
1717
                        break;
1718
                    default:
1719
                        return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1720
                            PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array or struct",
1721
                            'xmlrpcvals', $result->httpResponse());
1722
                }
1723
            }
1724
        }
1725
1726
        return $response;
1727
    }
1728
1729
    // *** BC layer ***
1730
1731
    /**
1732
     * @deprecated
1733
     *
1734
     * @param Request $req
1735
     * @param string $server
1736
     * @param int $port
1737
     * @param int $timeout
1738
     * @param string $username
1739
     * @param string $password
1740
     * @param int $authType
1741
     * @param string $proxyHost
1742
     * @param int $proxyPort
1743
     * @param string $proxyUsername
1744
     * @param string $proxyPassword
1745
     * @param int $proxyAuthType
1746
     * @param string $method
1747
     * @return Response
1748
     */
1749
    protected function sendPayloadHTTP10($req, $server, $port, $timeout = 0, $username = '', $password = '',
1750
        $authType = 1, $proxyHost = '', $proxyPort = 0, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1,
1751
        $method = 'http')
1752
    {
1753
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
1754
1755
        return $this->sendPayloadSocket($req, $server, $port, $timeout, $username, $password, $authType, null, null,
0 ignored issues
show
Deprecated Code introduced by
The function PhpXmlRpc\Client::sendPayloadSocket() has been deprecated. ( Ignorable by Annotation )

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

1755
        return /** @scrutinizer ignore-deprecated */ $this->sendPayloadSocket($req, $server, $port, $timeout, $username, $password, $authType, null, null,
Loading history...
1756
            null, null, $proxyHost, $proxyPort, $proxyUsername, $proxyPassword, $proxyAuthType, $method);
1757
    }
1758
1759
    /**
1760
     * @deprecated
1761
     *
1762
     * @param Request $req
1763
     * @param string $server
1764
     * @param int $port
1765
     * @param int $timeout
1766
     * @param string $username
1767
     * @param string $password
1768
     * @param int $authType
1769
     * @param string $cert
1770
     * @param string $certPass
1771
     * @param string $caCert
1772
     * @param string $caCertDir
1773
     * @param string $proxyHost
1774
     * @param int $proxyPort
1775
     * @param string $proxyUsername
1776
     * @param string $proxyPassword
1777
     * @param int $proxyAuthType
1778
     * @param bool $keepAlive
1779
     * @param string $key
1780
     * @param string $keyPass
1781
     * @param int $sslVersion
1782
     * @return Response
1783
     */
1784
    protected function sendPayloadHTTPS($req, $server, $port, $timeout = 0, $username = '', $password = '',
1785
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
1786
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $keepAlive = false, $key = '', $keyPass = '',
1787
        $sslVersion = 0)
1788
    {
1789
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
1790
1791
        return $this->sendPayloadCURL($req, $server, $port, $timeout, $username,
0 ignored issues
show
Deprecated Code introduced by
The function PhpXmlRpc\Client::sendPayloadCURL() has been deprecated. ( Ignorable by Annotation )

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

1791
        return /** @scrutinizer ignore-deprecated */ $this->sendPayloadCURL($req, $server, $port, $timeout, $username,
Loading history...
1792
            $password, $authType, $cert, $certPass, $caCert, $caCertDir, $proxyHost, $proxyPort,
1793
            $proxyUsername, $proxyPassword, $proxyAuthType, 'https', $keepAlive, $key, $keyPass, $sslVersion);
1794
    }
1795
1796
    /**
1797
     * @deprecated
1798
     *
1799
     * @param Request $req
1800
     * @param string $server
1801
     * @param int $port
1802
     * @param int $timeout
1803
     * @param string $username
1804
     * @param string $password
1805
     * @param int $authType only value supported is 1
1806
     * @param string $cert
1807
     * @param string $certPass
1808
     * @param string $caCert
1809
     * @param string $caCertDir
1810
     * @param string $proxyHost
1811
     * @param int $proxyPort
1812
     * @param string $proxyUsername
1813
     * @param string $proxyPassword
1814
     * @param int $proxyAuthType only value supported is 1
1815
     * @param string $method 'http' (synonym for 'http10'), 'http10' or 'https'
1816
     * @param string $key
1817
     * @param string $keyPass @todo not implemented yet.
1818
     * @param int $sslVersion
1819
     * @return Response
1820
     */
1821
    protected function sendPayloadSocket($req, $server, $port, $timeout = 0, $username = '', $password = '',
1822
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
1823
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'http', $key = '', $keyPass = '',
1824
        $sslVersion = 0)
1825
    {
1826
        $this->logDeprecationUnlessCalledBy('send');
1827
1828
        return $this->sendViaSocket($req, $method, $server, $port, $this->path, array(
1829
            'accepted_charset_encodings' => $this->accepted_charset_encodings,
1830
            'accepted_compression' => $this->accepted_compression,
1831
            'authtype' => $authType,
1832
            'cacert' => $caCert,
1833
            'cacertdir' => $caCertDir,
1834
            'cert' => $cert,
1835
            'certpass' => $certPass,
1836
            'cookies' => $this->cookies,
1837
            'debug' => $this->debug,
1838
            'extracurlopts' => $this->extracurlopts,
1839
            'extrasockopts' => $this->extrasockopts,
1840
            'keepalive' => $this->keepalive,
1841
            'key' => $key,
1842
            'keypass' => $keyPass,
1843
            'no_multicall' => $this->no_multicall,
1844
            'password' => $password,
1845
            'proxy' => $proxyHost,
1846
            'proxy_authtype' => $proxyAuthType,
1847
            'proxy_pass' => $proxyPassword,
1848
            'proxyport' => $proxyPort,
1849
            'proxy_user' => $proxyUsername,
1850
            'request_charset_encoding' => $this->request_charset_encoding,
1851
            'request_compression' => $this->request_compression,
1852
            'return_type' => $this->return_type,
1853
            'sslversion' => $sslVersion,
1854
            'timeout' => $timeout,
1855
            'username' => $username,
1856
            'user_agent' => $this->user_agent,
1857
            'use_curl' => $this->use_curl,
1858
            'verifyhost' => $this->verifyhost,
1859
            'verifypeer' => $this->verifypeer,
1860
        ));
1861
    }
1862
1863
    /**
1864
     * @deprecated
1865
     *
1866
     * @param Request $req
1867
     * @param string $server
1868
     * @param int $port
1869
     * @param int $timeout
1870
     * @param string $username
1871
     * @param string $password
1872
     * @param int $authType
1873
     * @param string $cert
1874
     * @param string $certPass
1875
     * @param string $caCert
1876
     * @param string $caCertDir
1877
     * @param string $proxyHost
1878
     * @param int $proxyPort
1879
     * @param string $proxyUsername
1880
     * @param string $proxyPassword
1881
     * @param int $proxyAuthType
1882
     * @param string $method 'http' (let curl decide), 'http10', 'http11', 'https', 'h2c' or 'h2'
1883
     * @param bool $keepAlive
1884
     * @param string $key
1885
     * @param string $keyPass
1886
     * @param int $sslVersion
1887
     * @return Response
1888
     */
1889
    protected function sendPayloadCURL($req, $server, $port, $timeout = 0, $username = '', $password = '',
1890
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
1891
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'https', $keepAlive = false, $key = '',
1892
        $keyPass = '', $sslVersion = 0)
1893
    {
1894
        $this->logDeprecationUnlessCalledBy('send');
1895
1896
        return $this->sendViaCURL($req, $method, $server, $port, $this->path, array(
1897
            'accepted_charset_encodings' => $this->accepted_charset_encodings,
1898
            'accepted_compression' => $this->accepted_compression,
1899
            'authtype' => $authType,
1900
            'cacert' => $caCert,
1901
            'cacertdir' => $caCertDir,
1902
            'cert' => $cert,
1903
            'certpass' => $certPass,
1904
            'cookies' => $this->cookies,
1905
            'debug' => $this->debug,
1906
            'extracurlopts' => $this->extracurlopts,
1907
            'extrasockopts' => $this->extrasockopts,
1908
            'keepalive' => $keepAlive,
1909
            'key' => $key,
1910
            'keypass' => $keyPass,
1911
            'no_multicall' => $this->no_multicall,
1912
            'password' => $password,
1913
            'proxy' => $proxyHost,
1914
            'proxy_authtype' => $proxyAuthType,
1915
            'proxy_pass' => $proxyPassword,
1916
            'proxyport' => $proxyPort,
1917
            'proxy_user' => $proxyUsername,
1918
            'request_charset_encoding' => $this->request_charset_encoding,
1919
            'request_compression' => $this->request_compression,
1920
            'return_type' => $this->return_type,
1921
            'sslversion' => $sslVersion,
1922
            'timeout' => $timeout,
1923
            'username' => $username,
1924
            'user_agent' => $this->user_agent,
1925
            'use_curl' => $this->use_curl,
1926
            'verifyhost' => $this->verifyhost,
1927
            'verifypeer' => $this->verifypeer,
1928
        ));
1929
    }
1930
1931
    /**
1932
     * @deprecated
1933
     *
1934
     * @param $req
1935
     * @param $server
1936
     * @param $port
1937
     * @param $timeout
1938
     * @param $username
1939
     * @param $password
1940
     * @param $authType
1941
     * @param $cert
1942
     * @param $certPass
1943
     * @param $caCert
1944
     * @param $caCertDir
1945
     * @param $proxyHost
1946
     * @param $proxyPort
1947
     * @param $proxyUsername
1948
     * @param $proxyPassword
1949
     * @param $proxyAuthType
1950
     * @param $method
1951
     * @param $keepAlive
1952
     * @param $key
1953
     * @param $keyPass
1954
     * @param $sslVersion
1955
     * @return false|\CurlHandle|resource
1956
     */
1957
    protected function prepareCurlHandle($req, $server, $port, $timeout = 0, $username = '', $password = '',
1958
         $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
1959
         $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'https', $keepAlive = false, $key = '',
1960
         $keyPass = '', $sslVersion = 0)
1961
    {
1962
        $this->logDeprecationUnlessCalledBy('sendViaCURL');
1963
1964
        return $this->createCURLHandle($req, $method, $server, $port, $this->path, array(
1965
            'accepted_charset_encodings' => $this->accepted_charset_encodings,
1966
            'accepted_compression' => $this->accepted_compression,
1967
            'authtype' => $authType,
1968
            'cacert' => $caCert,
1969
            'cacertdir' => $caCertDir,
1970
            'cert' => $cert,
1971
            'certpass' => $certPass,
1972
            'cookies' => $this->cookies,
1973
            'debug' => $this->debug,
1974
            'extracurlopts' => $this->extracurlopts,
1975
            'keepalive' => $keepAlive,
1976
            'key' => $key,
1977
            'keypass' => $keyPass,
1978
            'no_multicall' => $this->no_multicall,
1979
            'password' => $password,
1980
            'proxy' => $proxyHost,
1981
            'proxy_authtype' => $proxyAuthType,
1982
            'proxy_pass' => $proxyPassword,
1983
            'proxyport' => $proxyPort,
1984
            'proxy_user' => $proxyUsername,
1985
            'request_charset_encoding' => $this->request_charset_encoding,
1986
            'request_compression' => $this->request_compression,
1987
            'return_type' => $this->return_type,
1988
            'sslversion' => $sslVersion,
1989
            'timeout' => $timeout,
1990
            'username' => $username,
1991
            'user_agent' => $this->user_agent,
1992
            'use_curl' => $this->use_curl,
1993
            'verifyhost' => $this->verifyhost,
1994
            'verifypeer' => $this->verifypeer,
1995
        ));
1996
    }
1997
1998
    // we have to make this return by ref in order to allow calls such as `$resp->_cookies['name'] = ['value' => 'something'];`
1999
    public function &__get($name)
2000
    {
2001
        if (in_array($name, static::$options)) {
2002
            $this->logDeprecation('Getting property Client::' . $name . ' is deprecated');
2003
            return $this->$name;
2004
        }
2005
2006
        switch ($name) {
2007
            case 'errno':
2008
            case 'errstr':
2009
            case 'method':
2010
            case 'server':
2011
            case 'port':
2012
            case 'path':
2013
                $this->logDeprecation('Getting property Client::' . $name . ' is deprecated');
2014
                return $this->$name;
2015
            default:
2016
                /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout...
2017
                $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
2018
                trigger_error('Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
2019
                $result = null;
2020
                return $result;
2021
        }
2022
    }
2023
2024
    public function __set($name, $value)
2025
    {
2026
        if (in_array($name, static::$options)) {
2027
            $this->logDeprecation('Setting property Client::' . $name . ' is deprecated');
2028
            $this->$name = $value;
2029
            return;
2030
        }
2031
2032
        switch ($name) {
2033
            case 'errno':
2034
            case 'errstr':
2035
            case 'method':
2036
            case 'server':
2037
            case 'port':
2038
            case 'path':
2039
                $this->logDeprecation('Setting property Client::' . $name . ' is deprecated');
2040
                $this->$name = $value;
2041
                return;
2042
            default:
2043
                /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout...
2044
                $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
2045
                trigger_error('Undefined property via __set(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
2046
        }
2047
    }
2048
2049
    public function __isset($name)
2050
    {
2051
        if (in_array($name, static::$options)) {
2052
            $this->logDeprecation('Checking property Client::' . $name . ' is deprecated');
2053
            return isset($this->$name);
2054
        }
2055
2056
        switch ($name) {
2057
            case 'errno':
2058
            case 'errstr':
2059
            case 'method':
2060
            case 'server':
2061
            case 'port':
2062
            case 'path':
2063
                $this->logDeprecation('Checking property Client::' . $name . ' is deprecated');
2064
                return isset($this->$name);
2065
            default:
2066
                return false;
2067
        }
2068
    }
2069
2070
    public function __unset($name)
2071
    {
2072
        if (in_array($name, static::$options)) {
2073
            $this->logDeprecation('Unsetting property Client::' . $name . ' is deprecated');
2074
            unset($this->$name);
2075
            return;
2076
        }
2077
2078
        switch ($name) {
2079
            case 'errno':
2080
            case 'errstr':
2081
            case 'method':
2082
            case 'server':
2083
            case 'port':
2084
            case 'path':
2085
                $this->logDeprecation('Unsetting property Client::' . $name . ' is deprecated');
2086
                unset($this->$name);
2087
                return;
2088
            default:
2089
                /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout...
2090
                $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
2091
                trigger_error('Undefined property via __unset(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
2092
        }
2093
    }
2094
}
2095