Passed
Pull Request — master (#120)
by Michele
12:45
created

Client::sendPayloadSocket()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 39
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 7
Bugs 1 Features 0
Metric Value
cc 1
eloc 33
c 7
b 1
f 0
nc 1
nop 20
dl 0
loc 39
ccs 0
cts 0
cp 0
crap 2
rs 9.392

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

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

877
            $r = /** @scrutinizer ignore-deprecated */ $this->sendPayloadCURL(
Loading history...
878
                $req,
879
                $this->server,
880
                $this->port,
881
                $timeout,
882
                $this->username,
883
                $this->password,
884
                $this->authtype,
885
                $this->cert,
886 322
                $this->certpass,
887
                $this->cacert,
888
                $this->cacertdir,
889
                $this->proxy,
890
                $this->proxyport,
891 322
                $this->proxy_user,
892
                $this->proxy_pass,
893
                $this->proxy_authtype,
894
                // BC
895 322
                $method == 'http11' ? 'http' : $method,
896
                $this->keepalive,
897 96
                $this->key,
898 96
                $this->keypass,
899
                $this->sslversion
900
            );
901
        } else {
902
            $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

902
            $r = /** @scrutinizer ignore-deprecated */ $this->sendPayloadSocket(
Loading history...
903
                $req,
904 322
                $this->server,
905 322
                $this->port,
906
                $timeout,
907
                $this->username,
908
                $this->password,
909
                $this->authtype,
910 322
                $this->cert,
911
                $this->certpass,
912
                $this->cacert,
913
                $this->cacertdir,
914
                $this->proxy,
915 322
                $this->proxyport,
916
                $this->proxy_user,
917 322
                $this->proxy_pass,
918
                $this->proxy_authtype,
919
                $method,
920
                $this->key,
921
                $this->keypass,
922
                $this->sslversion
923
            );
924
        }
925
926
        return $r;
927
    }
928
929 322
    /**
930
     * @param Request $req
931
     * @param string $method
932 1
     * @param string $server
933 1
     * @param int $port
934 1
     * @param string $path
935 1
     * @param array $opts
936 1
     * @return Response
937
     */
938
    protected function sendViaSocket($req, $method, $server, $port, $path, $opts)
939 322
    {
940 160
        /// @todo log a warning if passed an unsupported method
941
942 322
        // Only create the payload if it was not created previously
943
        /// @todo what if the request's payload was created with a different encoding?
944 322
        ///       Also, if we do not call serialize(), the request will not set its content-type to have the charset declared
945
        $payload = $req->getPayload();
946
        if (empty($payload)) {
947
            $payload = $req->serialize($opts['request_charset_encoding']);
948
        }
949
950 322
        // Deflate request body and set appropriate request headers
951
        $encodingHdr = '';
952
        if ($opts['request_compression'] == 'gzip' || $opts['request_compression'] == 'deflate') {
953 323
            if ($opts['request_compression'] == 'gzip' && function_exists('gzencode')) {
954
                $a = @gzencode($payload);
955
                if ($a) {
956
                    $payload = $a;
957
                    $encodingHdr = "Content-Encoding: gzip\r\n";
958 323
                } else {
959 322
                    $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzencode failure in compressing request');
960 226
                }
961
            } else if (function_exists('gzcompress')) {
962 96
                $a = @gzcompress($payload);
963
                if ($a) {
964
                    $payload = $a;
965
                    $encodingHdr = "Content-Encoding: deflate\r\n";
966
                } else {
967 323
                    $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzcompress failure in compressing request');
968 302
                }
969
            } else {
970
                $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported by this PHP install');
971
            }
972 323
        } else {
973 323
            if ($opts['request_compression'] != '') {
974 64
                $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported');
975 32
            }
976 32
        }
977 32
978 32
        // thanks to Grant Rauscher
979
        $credentials = '';
980
        if ($opts['username'] != '') {
981 32
            if ($opts['authtype'] != 1) {
982 32
                $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported with HTTP 1.0');
983 32
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
984 64
                    PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': only Basic auth is supported with HTTP 1.0');
985
            }
986
            $credentials = 'Authorization: Basic ' . base64_encode($opts['username'] . ':' . $opts['password']) . "\r\n";
987
        }
988 259
989
        $acceptedEncoding = '';
990
        if (is_array($opts['accepted_compression']) && count($opts['accepted_compression'])) {
991 323
            $acceptedEncoding = 'Accept-Encoding: ' . implode(', ', $opts['accepted_compression']) . "\r\n";
992 323
        }
993 64
994
        if ($port == 0) {
995 259
            $port = ($method === 'https') ? 443 : 80;
996 32
        }
997
998
        $proxyCredentials = '';
999 227
        if ($opts['proxy']) {
1000
            if ($opts['proxyport'] == 0) {
1001
                $opts['proxyport'] = 8080;
1002 323
            }
1003 323
            $connectServer = $opts['proxy'];
1004 322
            $connectPort = $opts['proxyport'];
1005
            $transport = 'tcp';
1006
            /// @todo check: should we not use https in some cases?
1007 56
            $uri = 'http://' . $server . ':' . $port . $path;
1008
            if ($opts['proxy_user'] != '') {
1009
                if ($opts['proxy_authtype'] != 1) {
1010
                    $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported with HTTP 1.0');
1011 323
                    return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
1012
                        PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': only Basic auth to proxy is supported with HTTP 1.0');
1013 323
                }
1014
                $proxyCredentials = 'Proxy-Authorization: Basic ' . base64_encode($opts['proxy_user'] . ':' .
1015
                    $opts['proxy_pass']) . "\r\n";
1016
            }
1017 323
        } else {
1018
            $connectServer = $server;
1019 323
            $connectPort = $port;
1020
            $transport = ($method === 'https') ? 'tls' : 'tcp';
1021 323
            $uri = $path;
1022
        }
1023
1024 323
        // Cookie generation, as per RFC 6265
1025
        // NB: the following code does not honour 'expires', 'path' and 'domain' cookie attributes set to client obj by the user...
1026
        $cookieHeader = '';
1027
        if (count($opts['cookies'])) {
1028 323
            $version = '';
1029
            foreach ($opts['cookies'] as $name => $cookie) {
1030
                /// @todo should we sanitize the cookie value on behalf of the user? See setCookie comments
1031 66
                $cookieHeader .= ' ' . $name . '=' . $cookie['value'] . ";";
1032 64
            }
1033
            $cookieHeader = 'Cookie:' . $version . substr($cookieHeader, 0, -1) . "\r\n";
1034 2
        }
1035
1036
        // omit port if default
1037
        if (($port == 80 && in_array($method, array('http', 'http10'))) || ($port == 443 && $method == 'https')) {
1038 323
            $port = '';
1039
        } else {
1040 323
            $port = ':' . $port;
1041 161
        }
1042
1043
        $op = 'POST ' . $uri . " HTTP/1.0\r\n" .
1044 323
            'User-Agent: ' . $opts['user_agent'] . "\r\n" .
1045 64
            'Host: ' . $server . $port . "\r\n" .
1046
            $credentials .
1047
            $proxyCredentials .
1048
            $acceptedEncoding .
1049
            $encodingHdr .
1050 323
            'Accept-Charset: ' . implode(',', $opts['accepted_charset_encodings']) . "\r\n" .
1051
            $cookieHeader .
1052 323
            'Content-Type: ' . $req->getContentType() . "\r\nContent-Length: " .
1053
            strlen($payload) . "\r\n\r\n" .
1054 323
            $payload;
1055 272
1056
        if ($opts['debug'] > 1) {
1057
            $this->getLogger()->debug("---SENDING---\n$op\n---END---");
1058 323
        }
1059 323
1060 32
        $contextOptions = array();
1061 32
        if ($method == 'https') {
1062 291
            if ($opts['cert'] != '') {
1063
                $contextOptions['ssl']['local_cert'] = $opts['cert'];
1064
                if ($opts['certpass'] != '') {
1065 291
                    $contextOptions['ssl']['passphrase'] = $opts['certpass'];
1066 32
                }
1067 32
            }
1068 259
            if ($opts['cacert'] != '') {
1069 32
                $contextOptions['ssl']['cafile'] = $opts['cacert'];
1070 32
            }
1071
            if ($opts['cacertdir'] != '') {
1072
                $contextOptions['ssl']['capath'] = $opts['cacertdir'];
1073 323
            }
1074 32
            if ($opts['key'] != '') {
1075 32
                $contextOptions['ssl']['local_pk'] = $opts['key'];
1076 32
            }
1077
            $contextOptions['ssl']['verify_peer'] = $opts['verifypeer'];
1078
            $contextOptions['ssl']['verify_peer_name'] = $opts['verifypeer'];
1079
1080
            if ($opts['sslversion'] != 0) {
1081
                /// @see https://www.php.net/manual/en/function.curl-setopt.php, https://www.php.net/manual/en/migration56.openssl.php
1082 323
                switch($opts['sslversion']) {
1083
                    /// @todo what does this map to? 1.0-1.3?
1084 96
                    //case 1: // TLSv1
1085
                    //    break;
1086
                    case 2: // SSLv2
1087
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_SSLv2_CLIENT;
1088 96
                        break;
1089
                    case 3: // SSLv3
1090
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_SSLv3_CLIENT;
1091
                        break;
1092 96
                    case 4: // TLSv1.0
1093
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT;
1094 96
                        break;
1095
                    case 5: // TLSv1.1
1096
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
1097 96
                        break;
1098
                    case 6: // TLSv1.2
1099
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
1100
                        break;
1101 96
                    case 7: // TLSv1.3
1102
                        if (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT')) {
1103
                            $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT;
1104
                        } else {
1105 96
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
1106
                                PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': TLS-1.3 only is supported with PHP 7.4 or later');
1107
                        }
1108
                        break;
1109
                    default:
1110 96
                        return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
1111
                            PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': Unsupported required TLS version');
1112 96
                }
1113
            }
1114
        }
1115
1116 323
        foreach ($opts['extracurlopts'] as $proto => $protoOpts) {
1117 64
            foreach ($protoOpts as $key => $val) {
1118
                $contextOptions[$proto][$key] = $val;
1119
            }
1120 64
        }
1121 64
1122
        $context = stream_context_create($contextOptions);
1123
1124
        if ($opts['timeout'] <= 0) {
1125
            $connectTimeout = ini_get('default_socket_timeout');
1126
        } else {
1127
            $connectTimeout = $opts['timeout'];
1128
        }
1129
1130
        $this->errno = 0;
1131
        $this->errstr = '';
1132
1133 323
        $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr, $connectTimeout,
1134 271
            STREAM_CLIENT_CONNECT, $context);
1135 271
        if ($fp) {
0 ignored issues
show
introduced by
$fp is of type false|resource, thus it always evaluated to false.
Loading history...
1136 271
            if ($opts['timeout'] > 0) {
1137
                stream_set_timeout($fp, $opts['timeout'], 0);
1138 271
            }
1139
        } else {
1140
            if ($this->errstr == '') {
1141 323
                $err = error_get_last();
1142
                $this->errstr = $err['message'];
1143
            }
1144
1145 323
            $this->errstr = 'Connect error: ' . $this->errstr;
1146
            $r = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr . ' (' . $this->errno . ')');
1147
1148
            return $r;
1149 323
        }
1150
1151
        if (!fputs($fp, $op, strlen($op))) {
1152
            fclose($fp);
1153
            $this->errstr = 'Write error';
1154
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr);
1155
        }
1156
1157
        // Close socket before parsing.
1158
        // It should yield slightly better execution times, and make easier recursive calls (e.g. to follow http redirects)
1159
        $ipd = '';
1160
        do {
1161
            // shall we check for $data === FALSE?
1162
            // as per the manual, it signals an error
1163
            $ipd .= fread($fp, 32768);
1164
        } while (!feof($fp));
1165
        fclose($fp);
1166
1167
        return $req->parseResponse($ipd, false, $opts['return_type']);
1168
    }
1169
1170
    /**
1171
     * Contributed by Justin Miller
1172
     * Requires curl to be built into PHP
1173
     * NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers!
1174 66
     *
1175
     * @param Request $req
1176 66
     * @param string $method
1177
     * @param string $server
1178
     * @param int $port
1179 66
     * @param string $path
1180 45
     * @param array $opts the keys/values match self::getOptions
1181 45
     * @return Response
1182
     *
1183 45
     * @todo the $path arg atm is ignored. What to do if it is != $this->path?
1184
     */
1185
    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

1185
    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...
1186
    {
1187
        if (!function_exists('curl_init')) {
1188
            $this->errstr = 'CURL unavailable on this install';
1189
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_curl'], PhpXmlRpc::$xmlrpcstr['no_curl']);
1190
        }
1191
        if ($method == 'https' || $method == 'h2') {
1192
            // q: what about installs where we get back a string, but curl is linked to other ssl libs than openssl?
1193
            if (($info = curl_version()) &&
1194
                ((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...
1195
            ) {
1196
                $this->errstr = 'SSL unavailable on this install';
1197
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_ssl'], PhpXmlRpc::$xmlrpcstr['no_ssl']);
1198
            }
1199
        }
1200
        if (($method == 'h2' && !defined('CURL_HTTP_VERSION_2_0')) ||
1201 23
            ($method == 'h2c' && !defined('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE'))) {
1202
            $this->errstr = 'HTTP/2 unavailable on this install';
1203
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_http2'], PhpXmlRpc::$xmlrpcstr['no_http2']);
1204 23
        }
1205 23
1206
        // BC - we go through prepareCurlHandle in case some subclass reimplemented it
1207
        $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

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

1245
            $resp = $req->parseResponse(/** @scrutinizer ignore-type */ $result, true, $opts['return_type']);
Loading history...
1246 45
            if ($opts['keepalive']) {
1247 45
                /// @todo if we got back a 302 or 308, we should not reuse the curl handle for later calls
1248
                if ($resp->faultCode() == PhpXmlRpc::$xmlrpcerr['http_error']) {
1249 45
                    curl_close($curl);
1250 45
                    $this->xmlrpc_curl_handle = null;
1251
                }
1252
            }
1253 45
        }
1254
1255 45
        return $resp;
1256
    }
1257
1258
    /**
1259
     * @param Request $req
1260
     * @param string $method
1261 45
     * @param string $server
1262
     * @param int $port
1263 45
     * @param string $path
1264
     * @param array $opts the keys/values match self::getOptions
1265 45
     * @return \CurlHandle|resource|false
1266
     *
1267 22
     * @todo allow this method to either throw or return a Response, so that we can pass back to caller more info on errors
1268 22
     */
1269
    protected function createCURLHandle($req, $method, $server, $port, $path, $opts)
1270
    {
1271 22
        if ($port == 0) {
1272 22
            if (in_array($method, array('http', 'http10', 'http11', 'h2c'))) {
1273
                $port = 80;
1274
            } else {
1275
                $port = 443;
1276 22
            }
1277 22
        }
1278 22
1279 22
        // Only create the payload if it was not created previously
1280
        $payload = $req->getPayload();
1281
        if (empty($payload)) {
1282 22
            $payload = $req->serialize($opts['request_charset_encoding']);
1283 22
        }
1284 22
1285
        // Deflate request body and set appropriate request headers
1286
        $encodingHdr = '';
1287
        if (($opts['request_compression'] == 'gzip' || $opts['request_compression'] == 'deflate')) {
1288 22
            if ($opts['request_compression'] == 'gzip' && function_exists('gzencode')) {
1289 22
                $a = @gzencode($payload);
1290 22
                if ($a) {
1291
                    $payload = $a;
1292 22
                    $encodingHdr = 'Content-Encoding: gzip';
1293 22
                } else {
1294
                    $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzencode failure in compressing request');
1295
                }
1296 22
            } else if (function_exists('gzcompress')) {
1297 22
                $a = @gzcompress($payload);
1298
                if ($a) {
1299
                    $payload = $a;
1300 22
                    $encodingHdr = 'Content-Encoding: deflate';
1301 22
                } else {
1302
                    $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzcompress failure in compressing request');
1303
                }
1304
            } else {
1305
                $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported by this PHP install');
1306
            }
1307 22
        } else {
1308
            if ($opts['request_compression'] != '') {
1309
                $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported');
1310
            }
1311 24
        }
1312 24
1313
        if (!$opts['keepalive'] || !$this->xmlrpc_curl_handle) {
1314
            if ($method == 'http11' || $method == 'http10' || $method == 'h2c') {
1315 24
                $protocol = 'http';
1316 24
            } else {
1317
                if ($method == 'h2') {
1318
                    $protocol = 'https';
1319
                } else {
1320 24
                    // http, https
1321 24
                    $protocol = $method;
1322 24
                    if (strpos($protocol, ':') !== false) {
1323 24
                        $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ": warning - attempted hacking attempt?. The curl protocol requested for the call is: '$protocol'");
1324 24
                        return false;
1325
                    }
1326
                }
1327
            }
1328 24
            $curl = curl_init($protocol . '://' . $server . ':' . $port . $path);
1329 24
            if (!$curl) {
1330 22
                return false;
1331 22
            }
1332
            if ($opts['keepalive']) {
1333 22
                $this->xmlrpc_curl_handle = $curl;
1334
            }
1335
        } else {
1336 22
            $curl = $this->xmlrpc_curl_handle;
1337
        }
1338 22
1339
        // results into variable
1340
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
1341 22
1342 22
        if ($opts['debug'] > 1) {
1343
            curl_setopt($curl, CURLOPT_VERBOSE, true);
1344
            /// @todo redirect curlopt_stderr to some stream which can be piped to the logger
1345
        }
1346
        curl_setopt($curl, CURLOPT_USERAGENT, $opts['user_agent']);
1347
        // required for XMLRPC: post the data
1348 24
        curl_setopt($curl, CURLOPT_POST, 1);
1349
        // the data
1350
        curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
1351
1352
        // return the header too
1353
        curl_setopt($curl, CURLOPT_HEADER, 1);
1354
1355
        // NB: if we set an empty string, CURL will add http header indicating
1356
        // ALL methods it is supporting. This is possibly a better option than letting the user tell what curl can / cannot do...
1357
        if (is_array($opts['accepted_compression']) && count($opts['accepted_compression'])) {
1358
            //curl_setopt($curl, CURLOPT_ENCODING, implode(',', $opts['accepted_compression']));
1359
            // empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1360
            if (count($opts['accepted_compression']) == 1) {
1361
                curl_setopt($curl, CURLOPT_ENCODING, $opts['accepted_compression'][0]);
1362
            } else {
1363
                curl_setopt($curl, CURLOPT_ENCODING, '');
1364
            }
1365
        }
1366
        // extra headers
1367
        $headers = array('Content-Type: ' . $req->getContentType(), 'Accept-Charset: ' . implode(',', $opts['accepted_charset_encodings']));
1368
        // if no keepalive is wanted, let the server know it in advance
1369
        if (!$opts['keepalive']) {
1370
            $headers[] = 'Connection: close';
1371
        }
1372
        // request compression header
1373
        if ($encodingHdr) {
1374
            $headers[] = $encodingHdr;
1375
        }
1376
1377
        if ($this->extra_headers !== array()) {
1378
            $headers = array_merge($headers, $this->extra_headers);
1379
        }
1380
1381
        // Fix the HTTP/1.1 417 Expectation Failed Bug (curl by default adds a 'Expect: 100-continue' header when POST
1382
        // size exceeds 1025 bytes, apparently)
1383
        $headers[] = 'Expect:';
1384
1385
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
1386
        // timeout is borked
1387
        if ($opts['timeout']) {
1388
            curl_setopt($curl, CURLOPT_TIMEOUT, $opts['timeout'] == 1 ? 1 : $opts['timeout'] - 1);
1389
        }
1390
1391
        switch ($method) {
1392
            case 'http10':
1393
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
1394
                break;
1395
            case 'http11':
1396
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
1397
                break;
1398
            case 'h2c':
1399
                if (defined('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE')) {
1400
                    curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE);
1401
                } else {
1402
                    $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. HTTP2 is not supported by the current PHP/curl install');
1403
                    curl_close($curl);
1404
                    return false;
1405
                }
1406
                break;
1407
            case 'h2':
1408
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
1409
                break;
1410
        }
1411
1412
        if ($opts['username'] && $opts['password']) {
1413
            curl_setopt($curl, CURLOPT_USERPWD, $opts['username'] . ':' . $opts['password']);
1414
            if (defined('CURLOPT_HTTPAUTH')) {
1415
                curl_setopt($curl, CURLOPT_HTTPAUTH, $opts['authtype']);
1416
            } elseif ($opts['authtype'] != 1) {
1417
                $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported by the current PHP/curl install');
1418
                curl_close($curl);
1419
                return false;
1420
            }
1421
        }
1422
1423
        // note: h2c is http2 without the https. No need to have it in this IF
1424
        if ($method == 'https' || $method == 'h2') {
1425
            // set cert file
1426
            if ($opts['cert']) {
1427
                curl_setopt($curl, CURLOPT_SSLCERT, $opts['cert']);
1428
            }
1429
            // set cert password
1430
            if ($opts['certpass']) {
1431
                curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $opts['certpass']);
1432
            }
1433
            // whether to verify remote host's cert
1434
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $opts['verifypeer']);
1435
            // set ca certificates file/dir
1436
            if ($opts['cacert']) {
1437
                curl_setopt($curl, CURLOPT_CAINFO, $opts['cacert']);
1438
            }
1439
            if ($opts['cacertdir']) {
1440
                curl_setopt($curl, CURLOPT_CAPATH, $opts['cacertdir']);
1441
            }
1442
            // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1443
            if ($opts['key']) {
1444
                curl_setopt($curl, CURLOPT_SSLKEY, $opts['key']);
1445
            }
1446
            // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1447
            if ($opts['keypass']) {
1448
                curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $opts['keypass']);
1449
            }
1450
            // whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that
1451
            // it matches the hostname used
1452
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $opts['verifyhost']);
1453
            // allow usage of different SSL versions
1454
            curl_setopt($curl, CURLOPT_SSLVERSION, $opts['sslversion']);
1455
        }
1456
1457
        // proxy info
1458
        if ($opts['proxy']) {
1459
            if ($opts['proxyport'] == 0) {
1460
                $opts['proxyport'] = 8080; // NB: even for HTTPS, local connection is on port 8080
1461
            }
1462
            curl_setopt($curl, CURLOPT_PROXY, $opts['proxy'] . ':' . $opts['proxyport']);
1463
            if ($opts['proxy_user']) {
1464
                curl_setopt($curl, CURLOPT_PROXYUSERPWD, $opts['proxy_user'] . ':' . $opts['proxy_pass']);
1465
                if (defined('CURLOPT_PROXYAUTH')) {
1466
                    curl_setopt($curl, CURLOPT_PROXYAUTH, $opts['proxy_authtype']);
1467
                } elseif ($opts['proxy_authtype'] != 1) {
1468
                    $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported by the current PHP/curl install');
1469
                    curl_close($curl);
1470
                    return false;
1471
                }
1472
            }
1473
        }
1474
1475
        // NB: should we build cookie http headers by hand rather than let CURL do it?
1476
        // NB: the following code does not honour 'expires', 'path' and 'domain' cookie attributes set to client obj by the user...
1477
        if (count($opts['cookies'])) {
1478
            $cookieHeader = '';
1479
            foreach ($opts['cookies'] as $name => $cookie) {
1480
                $cookieHeader .= $name . '=' . $cookie['value'] . '; ';
1481
            }
1482
            curl_setopt($curl, CURLOPT_COOKIE, substr($cookieHeader, 0, -2));
1483
        }
1484
1485
        foreach ($opts['extracurlopts'] as $opt => $val) {
1486
            curl_setopt($curl, $opt, $val);
1487
        }
1488
1489
        if ($opts['debug'] > 1) {
1490
            $this->getLogger()->debug("---SENDING---\n$payload\n---END---");
1491
        }
1492
1493
        return $curl;
1494
    }
1495
1496
    /**
1497
     * Send an array of requests and return an array of responses.
1498
     *
1499
     * Unless $this->no_multicall has been set to true, it will try first to use one single xml-rpc call to server method
1500
     * system.multicall, and revert to sending many successive calls in case of failure.
1501
     * This failure is also stored in $this->no_multicall for subsequent calls.
1502
     * Unfortunately, there is no server error code universally used to denote the fact that multicall is unsupported,
1503
     * so there is no way to reliably distinguish between that and a temporary failure.
1504
     * If you are sure that server supports multicall and do not want to fallback to using many single calls, set the
1505
     * 2np parameter to FALSE.
1506
     *
1507
     * NB: trying to shoehorn extra functionality into existing syntax has resulted
1508
     * in pretty much convoluted code...
1509
     *
1510
     * @param Request[] $reqs an array of Request objects
1511
     * @param bool $noFallback When true, upon receiving an error during multicall, multiple single calls will not be
1512
     *                         attempted.
1513
     *                         Deprecated alternative, was: int - "connection timeout (in seconds). See the details in the
1514
     *                         docs for the send() method". Please use setOption instead to set a timeout
1515
     * @param string $method deprecated. Was: "the http protocol variant to be used. See the details in the docs for the send() method."
1516
     *                       Please use the constructor to set an http protocol variant.
1517
     * @param boolean $fallback deprecated. Was: "w"hen true, upon receiving an error during multicall, multiple single
1518
     *                          calls will be attempted"
1519
     * @return Response[]
1520
     */
1521
    public function multicall($reqs, $timeout = 0, $method = '', $fallback = true)
1522
    {
1523
        // BC
1524
        if (is_bool($timeout) && $fallback === true) {
1525
            $fallback = !$timeout;
1526
            $timeout = 0;
1527
        }
1528
1529
        if ($method == '') {
1530
            $method = $this->method;
1531
        }
1532
1533
        if (!$this->no_multicall) {
1534
            $results = $this->_try_multicall($reqs, $timeout, $method);
1535
            /// @todo how to handle the case of $this->return_type = xml?
1536
            if (is_array($results)) {
1537
                // System.multicall succeeded
1538
                return $results;
1539
            } else {
1540
                // either system.multicall is unsupported by server, or the call failed for some other reason.
1541
                // Feature creep: is there a way to tell apart unsupported multicall from other faults?
1542
                if ($fallback) {
1543
                    // Don't try it next time...
1544
                    $this->no_multicall = true;
1545
                } else {
1546
                    $result = $results;
1547
                }
1548
            }
1549
        } else {
1550
            // override fallback, in case careless user tries to do two
1551
            // opposite things at the same time
1552
            $fallback = true;
1553
        }
1554
1555
        $results = array();
1556
        if ($fallback) {
1557
            // system.multicall is (probably) unsupported by server: emulate multicall via multiple requests
1558
            /// @todo use curl multi_ functions to make this quicker (see the implementation in the parallel.php demo)
1559
            foreach ($reqs as $req) {
1560
                $results[] = $this->send($req, $timeout, $method);
1561
            }
1562
        } else {
1563
            // user does NOT want to fallback on many single calls: since we should always return an array of responses,
1564
            // we return an array with the same error repeated n times
1565
            foreach ($reqs as $req) {
1566
                $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...
1567
            }
1568
        }
1569
1570
        return $results;
1571
    }
1572
1573
    /**
1574
     * Attempt to boxcar $reqs via system.multicall.
1575
     *
1576
     * @param Request[] $reqs
1577
     * @param int $timeout
1578
     * @param string $method
1579
     * @return Response[]|Response a single Response when the call returned a fault / does not conform to what we expect
1580
     *                             from a multicall response
1581
     */
1582
    private function _try_multicall($reqs, $timeout, $method)
1583
    {
1584
        // Construct multicall request
1585
        $calls = array();
1586
        foreach ($reqs as $req) {
1587
            $call['methodName'] = new Value($req->method(), 'string');
1588
            $numParams = $req->getNumParams();
1589
            $params = array();
1590
            for ($i = 0; $i < $numParams; $i++) {
1591
                $params[$i] = $req->getParam($i);
1592
            }
1593
            $call['params'] = new Value($params, 'array');
1594
            $calls[] = new Value($call, 'struct');
1595
        }
1596
        $multiCall = new static::$requestClass('system.multicall');
1597
        $multiCall->addParam(new Value($calls, 'array'));
1598
1599
        // Attempt RPC call
1600
        $result = $this->send($multiCall, $timeout, $method);
1601
1602
        if ($result->faultCode() != 0) {
1603
            // call to system.multicall failed
1604
            return $result;
1605
        }
1606
1607
        // Unpack responses.
1608
        $rets = $result->value();
1609
        $response = array();
1610
1611
        if ($this->return_type == 'xml') {
1612
            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...
1613
                $response[] = new static::$responseClass($rets, 0, '', 'xml', $result->httpResponse());
1614
            }
1615
1616
        } elseif ($this->return_type == 'phpvals') {
1617
            if (!is_array($rets)) {
1618
                // bad return type from system.multicall
1619
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1620
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': not an array', 'phpvals', $result->httpResponse());
1621
            }
1622
            $numRets = count($rets);
1623
            if ($numRets != count($reqs)) {
1624
                // wrong number of return values.
1625
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1626
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': incorrect number of responses', 'phpvals',
1627
                    $result->httpResponse());
1628
            }
1629
1630
            for ($i = 0; $i < $numRets; $i++) {
1631
                $val = $rets[$i];
1632
                if (!is_array($val)) {
1633
                    return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1634
                        PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array or struct",
1635
                        'phpvals', $result->httpResponse());
1636
                }
1637
                switch (count($val)) {
1638
                    case 1:
1639
                        if (!isset($val[0])) {
1640
                            // Bad value
1641
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1642
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has no value",
1643
                                'phpvals', $result->httpResponse());
1644
                        }
1645
                        // Normal return value
1646
                        $response[$i] = new static::$responseClass($val[0], 0, '', 'phpvals', $result->httpResponse());
1647
                        break;
1648
                    case 2:
1649
                        /// @todo remove usage of @: it is apparently quite slow
1650
                        $code = @$val['faultCode'];
1651
                        if (!is_int($code)) {
1652
                            /// @todo should we check that it is != 0?
1653
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1654
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1655
                                'phpvals', $result->httpResponse());
1656
                        }
1657
                        $str = @$val['faultString'];
1658
                        if (!is_string($str)) {
1659
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1660
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no FaultString",
1661
                                'phpvals', $result->httpResponse());
1662
                        }
1663
                        $response[$i] = new static::$responseClass(0, $code, $str, 'phpvals', $result->httpResponse());
1664
                        break;
1665
                    default:
1666
                        return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1667
                            PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1668
                            'phpvals', $result->httpResponse());
1669
                }
1670
            }
1671
1672
        } else {
1673
            // return type == 'xmlrpcvals'
1674
            if ($rets->kindOf() != 'array') {
1675
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1676
                    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 1586. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
1677
                    $result->httpResponse());
1678
            }
1679
            $numRets = $rets->count();
1680
            if ($numRets != count($reqs)) {
1681
                // wrong number of return values.
1682
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1683
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': incorrect number of responses', 'xmlrpcvals',
1684
                    $result->httpResponse());
1685
            }
1686
1687
            foreach ($rets as $i => $val) {
1688
                switch ($val->kindOf()) {
1689
                    case 'array':
1690
                        if ($val->count() != 1) {
1691
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1692
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1693
                                'phpvals', $result->httpResponse());
1694
                        }
1695
                        // Normal return value
1696
                        $response[] = new static::$responseClass($val[0], 0, '', 'xmlrpcvals', $result->httpResponse());
1697
                        break;
1698
                    case 'struct':
1699
                        if ($val->count() != 2) {
1700
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1701
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1702
                                'phpvals', $result->httpResponse());
1703
                        }
1704
                        /** @var Value $code */
1705
                        $code = $val['faultCode'];
1706
                        if ($code->kindOf() != 'scalar' || $code->scalarTyp() != 'int') {
1707
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1708
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1709
                                'xmlrpcvals', $result->httpResponse());
1710
                        }
1711
                        /** @var Value $str */
1712
                        $str = $val['faultString'];
1713
                        if ($str->kindOf() != 'scalar' || $str->scalarTyp() != 'string') {
1714
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1715
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1716
                                'xmlrpcvals', $result->httpResponse());
1717
                        }
1718
                        $response[] = new static::$responseClass(0, $code->scalarVal(), $str->scalarVal(), 'xmlrpcvals', $result->httpResponse());
1719
                        break;
1720
                    default:
1721
                        return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1722
                            PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array or struct",
1723
                            'xmlrpcvals', $result->httpResponse());
1724
                }
1725
            }
1726
        }
1727
1728
        return $response;
1729
    }
1730
1731
    // *** BC layer ***
1732
1733
    /**
1734
     * @deprecated
1735
     *
1736
     * @param Request $req
1737
     * @param string $server
1738
     * @param int $port
1739
     * @param int $timeout
1740
     * @param string $username
1741
     * @param string $password
1742
     * @param int $authType
1743
     * @param string $proxyHost
1744
     * @param int $proxyPort
1745
     * @param string $proxyUsername
1746
     * @param string $proxyPassword
1747
     * @param int $proxyAuthType
1748
     * @param string $method
1749
     * @return Response
1750
     */
1751
    protected function sendPayloadHTTP10($req, $server, $port, $timeout = 0, $username = '', $password = '',
1752
        $authType = 1, $proxyHost = '', $proxyPort = 0, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1,
1753
        $method = 'http')
1754
    {
1755
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
1756
1757
        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

1757
        return /** @scrutinizer ignore-deprecated */ $this->sendPayloadSocket($req, $server, $port, $timeout, $username, $password, $authType, null, null,
Loading history...
1758
            null, null, $proxyHost, $proxyPort, $proxyUsername, $proxyPassword, $proxyAuthType, $method);
1759
    }
1760
1761
    /**
1762
     * @deprecated
1763
     *
1764
     * @param Request $req
1765
     * @param string $server
1766
     * @param int $port
1767
     * @param int $timeout
1768
     * @param string $username
1769
     * @param string $password
1770
     * @param int $authType
1771
     * @param string $cert
1772
     * @param string $certPass
1773
     * @param string $caCert
1774
     * @param string $caCertDir
1775
     * @param string $proxyHost
1776
     * @param int $proxyPort
1777
     * @param string $proxyUsername
1778
     * @param string $proxyPassword
1779
     * @param int $proxyAuthType
1780
     * @param bool $keepAlive
1781
     * @param string $key
1782
     * @param string $keyPass
1783
     * @param int $sslVersion
1784
     * @return Response
1785
     */
1786
    protected function sendPayloadHTTPS($req, $server, $port, $timeout = 0, $username = '', $password = '',
1787
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
1788
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $keepAlive = false, $key = '', $keyPass = '',
1789
        $sslVersion = 0)
1790
    {
1791
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
1792
1793
        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

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