Passed
Push — master ( b9175a...f91eab )
by Gaetano
08:25
created

Client::sendPayloadCURL()   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 5
Bugs 2 Features 0
Metric Value
cc 1
eloc 33
nc 1
nop 21
dl 0
loc 39
ccs 0
cts 0
cp 0
crap 2
rs 9.392
c 5
b 2
f 0

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
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
     * CURL handle: used for keep-alive
263
     * @internal
264
     */
265
    public $xmlrpc_curl_handle = null;
266
267
    /**
268
     * @var array
269
     */
270
    protected static $options = array(
271
        self::OPT_ACCEPTED_CHARSET_ENCODINGS,
272
        self::OPT_ACCEPTED_COMPRESSION,
273
        self::OPT_AUTH_TYPE,
274
        self::OPT_CA_CERT,
275
        self::OPT_CA_CERT_DIR,
276
        self::OPT_CERT,
277
        self::OPT_CERT_PASS,
278
        self::OPT_COOKIES,
279
        self::OPT_DEBUG,
280
        self::OPT_EXTRA_CURL_OPTS,
281
        self::OPT_EXTRA_SOCKET_OPTS,
282
        self::OPT_KEEPALIVE,
283
        self::OPT_KEY,
284
        self::OPT_KEY_PASS,
285
        self::OPT_NO_MULTICALL,
286
        self::OPT_PASSWORD,
287
        self::OPT_PROXY,
288
        self::OPT_PROXY_AUTH_TYPE,
289
        self::OPT_PROXY_PASS,
290
        self::OPT_PROXY_USER,
291
        self::OPT_PROXY_PORT,
292
        self::OPT_REQUEST_CHARSET_ENCODING,
293
        self::OPT_REQUEST_COMPRESSION,
294
        self::OPT_RETURN_TYPE,
295
        self::OPT_SSL_VERSION,
296
        self::OPT_TIMEOUT,
297
        self::OPT_USE_CURL,
298
        self::OPT_USER_AGENT,
299
        self::OPT_USERNAME,
300
        self::OPT_VERIFY_HOST,
301
        self::OPT_VERIFY_PEER,
302
    );
303
304
    /**
305
     * @param string $path either the PATH part of the xml-rpc server URL, or complete server URL (in which case you
306
     *                     should use an empty string for all other parameters)
307
     *                     e.g. /xmlrpc/server.php
308
     *                     e.g. http://phpxmlrpc.sourceforge.net/server.php
309
     *                     e.g. https://james:[email protected]:444/xmlrpcserver?agent=007
310
     *                     e.g. h2://fast-and-secure-services.org/endpoint
311
     * @param string $server the server name / ip address
312
     * @param integer $port the port the server is listening on, when omitted defaults to 80 or 443 depending on
313
     *                      protocol used
314
     * @param string $method the http protocol variant: defaults to 'http'; 'https', 'http11', 'h2' and 'h2c' can
315
     *                       be used if CURL is installed. The value set here can be overridden in any call to $this->send().
316 132
     *                       Use 'h2' to make the lib attempt to use http/2 over a secure connection, and 'h2c'
317
     *                       for http/2 without tls. Note that 'h2c' will not use the h2c 'upgrade' method, and be
318 132
     *                       thus incompatible with any server/proxy not supporting http/2. This is because POST
319 132
     *                       request are not compatible with h2c upgrade.
320
     */
321
    public function __construct($path, $server = '', $port = '', $method = '')
322
    {
323
        // allow user to specify all params in $path
324
        if ($server == '' && $port == '' && $method == '') {
325
            $parts = parse_url($path);
326
            $server = $parts['host'];
327
            $path = isset($parts['path']) ? $parts['path'] : '';
328 132
            if (isset($parts['query'])) {
329
                $path .= '?' . $parts['query'];
330 132
            }
331 132
            if (isset($parts['fragment'])) {
332
                $path .= '#' . $parts['fragment'];
333
            }
334
            if (isset($parts['port'])) {
335
                $port = $parts['port'];
336
            }
337
            if (isset($parts['scheme'])) {
338 132
                $method = $parts['scheme'];
339
            }
340 132
            if (isset($parts['user'])) {
341 132
                $this->username = $parts['user'];
342
            }
343
            if (isset($parts['pass'])) {
344
                $this->password = $parts['pass'];
345
            }
346
        }
347
        if ($path == '' || $path[0] != '/') {
348
            $this->path = '/' . $path;
349
        } else {
350
            $this->path = $path;
351
        }
352
        $this->server = $server;
353
        if ($port != '') {
354
            $this->port = $port;
355 99
        }
356
        if ($method != '') {
357 99
            $this->method = $method;
358 99
        }
359 99
360 99
        // if ZLIB is enabled, let the client by default accept compressed responses
361 99
        if (function_exists('gzinflate') || (
362 99
                function_exists('curl_version') && (($info = curl_version()) &&
363
                    ((is_string($info) && strpos($info, 'zlib') !== null) || isset($info['libz_version'])))
364
            )
365
        ) {
366
            $this->accepted_compression = array('gzip', 'deflate');
367
        }
368
369
        // keepalives: enabled by default
370
        $this->keepalive = true;
371
372
        // by default the xml parser can support these 3 charset encodings
373
        $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII');
374
375
        // NB: this is disabled to avoid making all the requests sent huge... mbstring supports more than 80 charsets!
376
        //$ch = $this->getCharsetEncoder();
377
        //$this->accepted_charset_encodings = $ch->knownCharsets();
378
379
        // initialize user_agent string
380
        $this->user_agent = PhpXmlRpc::$xmlrpcName . ' ' . PhpXmlRpc::$xmlrpcVersion;
381
    }
382
383
    /**
384
     * @param string $name see all the OPT_ constants
385
     * @param mixed $value
386
     * @return $this
387
     * @throws ValueErrorException on unsupported option
388
     */
389
    public function setOption($name, $value)
390
    {
391
        if (in_array($name, static::$options)) {
392
            $this->$name = $value;
393
            return $this;
394
        }
395
396
        throw new ValueErrorException("Unsupported option '$name'");
397
    }
398
399
    /**
400
     * @param string $name see all the OPT_ constants
401
     * @return mixed
402
     * @throws ValueErrorException on unsupported option
403
     */
404
    public function getOption($name)
405
    {
406
        if (in_array($name, static::$options)) {
407
            return $this->$name;
408
        }
409
410
        throw new ValueErrorException("Unsupported option '$name'");
411
    }
412
413
    /**
414
     * Returns the complete list of Client options, with their value.
415
     * @return array
416
     */
417
    public function getOptions()
418 580
    {
419
        $values = array();
420 580
        foreach (static::$options as $opt) {
421 580
            $values[$opt] = $this->getOption($opt);
422
        }
423
        return $values;
424
    }
425
426
    /**
427 580
     * @param array $options key: any valid option (see all the OPT_ constants)
428
     * @return $this
429 580
     * @throws ValueErrorException on unsupported option
430
     */
431
    public function setOptions($options)
432
    {
433
        foreach ($options as $name => $value) {
434
            $this->setOption($name, $value);
435
        }
436
437
        return $this;
438
    }
439
440
    /**
441
     * Enable/disable the echoing to screen of the xml-rpc responses received. The default is not to output anything.
442
     *
443
     * The debugging information at level 1 includes the raw data returned from the XML-RPC server it was querying
444
     * (including bot HTTP headers and the full XML payload), and the PHP value the client attempts to create to
445
     * represent the value returned by the server.
446 66
     * At level 2, the complete payload of the xml-rpc request is also printed, before being sent to the server.
447
     * At level -1, the Response objects returned by send() calls will not carry information about the http response's
448 66
     * cookies, headers and body, which might save some memory
449 66
     *
450
     * This option can be very useful when debugging servers as it allows you to see exactly what the client sends and
451
     * the server returns. Never leave it enabled for production!
452
     *
453
     * @param integer $level values -1, 0, 1 and 2 are supported
454
     * @return $this
455
     */
456
    public function setDebug($level)
457
    {
458
        $this->debug = $level;
459
        return $this;
460
    }
461
462
    /**
463
     * Sets the username and password for authorizing the client to the server.
464
     *
465
     * With the default (HTTP) transport, this information is used for HTTP Basic authorization.
466
     * Note that username and password can also be set using the class constructor.
467
     * With HTTP 1.1 and HTTPS transport, NTLM and Digest authentication protocols are also supported. To enable them use
468
     * the constants CURLAUTH_DIGEST and CURLAUTH_NTLM as values for the auth type parameter.
469
     *
470
     * @param string $user username
471
     * @param string $password password
472
     * @param integer $authType auth type. See curl_setopt man page for supported auth types. Defaults to CURLAUTH_BASIC
473
     *                          (basic auth). Note that auth types NTLM and Digest will only work if the Curl php
474
     *                          extension is enabled.
475
     * @return $this
476
     */
477
    public function setCredentials($user, $password, $authType = 1)
478
    {
479
        $this->username = $user;
480
        $this->password = $password;
481
        $this->authtype = $authType;
482
        return $this;
483
    }
484
485
    /**
486
     * Set the optional certificate and passphrase used in SSL-enabled communication with a remote server.
487
     *
488
     * Note: to retrieve information about the client certificate on the server side, you will need to look into the
489
     * environment variables which are set up by the webserver. Different webservers will typically set up different
490
     * variables.
491
     *
492
     * @param string $cert the name of a file containing a PEM formatted certificate
493
     * @param string $certPass the password required to use it
494
     * @return $this
495 697
     */
496
    public function setCertificate($cert, $certPass = '')
497
    {
498
        $this->cert = $cert;
499 697
        $this->certpass = $certPass;
500 117
        return $this;
501
    }
502
503 697
    /**
504
     * Add a CA certificate to verify server with in SSL-enabled communication when SetSSLVerifypeer has been set to TRUE.
505 66
     *
506
     * See the php manual page about CURLOPT_CAINFO for more details.
507 66
     *
508 697
     * @param string $caCert certificate file name (or dir holding certificates)
509 28
     * @param bool $isDir set to true to indicate cacert is a dir. defaults to false
510 28
     * @return $this
511 28
     */
512
    public function setCaCertificate($caCert, $isDir = false)
513
    {
514
        if ($isDir) {
515 697
            $this->cacertdir = $caCert;
516
        } else {
517
            $this->cacert = $caCert;
518
        }
519 697
        return $this;
520 697
    }
521
522 697
    /**
523 322
     * Set attributes for SSL communication: private SSL key.
524 322
     *
525 322
     * NB: does not work in older php/curl installs.
526 322
     * Thanks to Daniel Convissor.
527
     *
528 322
     * @param string $key The name of a file containing a private SSL key
529 322
     * @param string $keyPass The secret password needed to use the private SSL key
530 322
     * @return $this
531 322
     */
532 322
    public function setKey($key, $keyPass)
533 322
    {
534 322
        $this->key = $key;
535 322
        $this->keypass = $keyPass;
536 322
        return $this;
537 322
    }
538 322
539 322
    /**
540
     * Set attributes for SSL communication: verify the remote host's SSL certificate, and cause the connection to fail
541 322
     * if the cert verification fails.
542 322
     *
543 322
     * By default, verification is enabled.
544 322
     * To specify custom SSL certificates to validate the server with, use the setCaCertificate method.
545 322
     *
546
     * @param bool $i enable/disable verification of peer certificate
547
     * @return $this
548
     * @deprecated use setOption
549 375
     */
550 375
    public function setSSLVerifyPeer($i)
551 375
    {
552 375
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
553
554 375
        $this->verifypeer = $i;
555 375
        return $this;
556 375
    }
557 375
558 375
    /**
559 375
     * Set attributes for SSL communication: verify the remote host's SSL certificate's common name (CN).
560 375
     *
561 375
     * Note that support for value 1 has been removed in cURL 7.28.1
562 375
     *
563 375
     * @param int $i Set to 1 to only the existence of a CN, not that it matches
564 375
     * @return $this
565 375
     * @deprecated use setOption
566
     */
567 375
    public function setSSLVerifyHost($i)
568 375
    {
569 375
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
570
571
        $this->verifyhost = $i;
572
        return $this;
573 697
    }
574
575
    /**
576
     * Set attributes for SSL communication: SSL version to use. Best left at 0 (default value): let cURL decide
577
     *
578
     * @param int $i
579
     * @return $this
580
     * @deprecated use setOption
581
     */
582
    public function setSSLVersion($i)
583
    {
584
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
585
586
        $this->sslversion = $i;
587
        return $this;
588
    }
589
590
    /**
591
     * Set proxy info.
592
     *
593
     * NB: CURL versions before 7.11.10 cannot use a proxy to communicate with https servers.
594
     *
595
     * @param string $proxyHost
596
     * @param string $proxyPort Defaults to 8080 for HTTP and 443 for HTTPS
597
     * @param string $proxyUsername Leave blank if proxy has public access
598
     * @param string $proxyPassword Leave blank if proxy has public access
599
     * @param int $proxyAuthType defaults to CURLAUTH_BASIC (Basic authentication protocol); set to constant CURLAUTH_NTLM
600
     *                           to use NTLM auth with proxy (has effect only when the client uses the HTTP 1.1 protocol)
601
     * @return $this
602
     */
603
    public function setProxy($proxyHost, $proxyPort, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1)
604
    {
605
        $this->proxy = $proxyHost;
606
        $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...
607
        $this->proxy_user = $proxyUsername;
608
        $this->proxy_pass = $proxyPassword;
609
        $this->proxy_authtype = $proxyAuthType;
610
        return $this;
611
    }
612
613
    /**
614
     * Enables/disables reception of compressed xml-rpc responses.
615
     *
616
     * This requires the "zlib" extension to be enabled in your php install. If it is, by default xmlrpc_client
617
     * instances will enable reception of compressed content.
618
     * Note that enabling reception of compressed responses merely adds some standard http headers to xml-rpc requests.
619
     * It is up to the xml-rpc server to return compressed responses when receiving such requests.
620
     *
621
     * @param string $compMethod either 'gzip', 'deflate', 'any' or ''
622
     * @return $this
623
     */
624
    public function setAcceptedCompression($compMethod)
625
    {
626
        if ($compMethod == 'any') {
627
            $this->accepted_compression = array('gzip', 'deflate');
628
        } 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...
629
            $this->accepted_compression = array();
630
        } else {
631
            $this->accepted_compression = array($compMethod);
632
        }
633
        return $this;
634
    }
635
636
    /**
637
     * Enables/disables http compression of xml-rpc request.
638
     *
639
     * This requires the "zlib" extension to be enabled in your php install.
640
     * Take care when sending compressed requests: servers might not support them (and automatic fallback to
641
     * uncompressed requests is not yet implemented).
642
     *
643
     * @param string $compMethod either 'gzip', 'deflate' or ''
644
     * @return $this
645
     * @deprecated use setOption
646
     */
647
    public function setRequestCompression($compMethod)
648
    {
649
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
650
651
        $this->request_compression = $compMethod;
652
        return $this;
653
    }
654
655
    /**
656
     * Adds a cookie to list of cookies that will be sent to server with every further request (useful e.g. for keeping
657
     * session info outside the xml-rpc payload).
658
     *
659
     * NB: by default all cookies set via this method are sent to the server, regardless of path/domain/port. Taking
660
     * advantage of those values is left to the single developer.
661
     *
662
     * @param string $name nb: will not be escaped in the request's http headers. Take care not to use CTL chars or
663
     *                     separators!
664 375
     * @param string $value
665
     * @param string $path
666
     * @param string $domain
667
     * @param int $port do not use! Cookies are not separated by port
668
     * @return $this
669
     *
670
     * @todo check correctness of urlencoding cookie value (copied from php way of doing it, but php is generally sending
671 375
     *       response not requests. We do the opposite...)
672 373
     * @todo strip invalid chars from cookie name? As per RFC6265, we should follow RFC2616, Section 2.2
673
     * @todo drop/rename $port parameter. Cookies are not isolated by port!
674
     * @todo feature-creep allow storing 'expires', 'secure', 'httponly' and 'samesite' cookie attributes (we could do
675
     *       as php, and allow $path to be an array of attributes...)
676 375
     */
677 346
    public function setCookie($name, $value = '', $path = '', $domain = '', $port = null)
678
    {
679
        $this->cookies[$name]['value'] = rawurlencode($value);
680 375
        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...
681
            $this->cookies[$name]['path'] = $path;
682 375
            $this->cookies[$name]['domain'] = $domain;
683 375
            $this->cookies[$name]['port'] = $port;
684 64
        }
685 32
        return $this;
686 32
    }
687 32
688 32
    /**
689
     * Directly set cURL options, for extra flexibility (when in cURL mode).
690
     *
691 32
     * It allows e.g. to bind client to a specific IP interface / address.
692 32
     *
693 32
     * @param array $options
694 32
     * @return $this
695
     * @deprecated use setOption
696
     */
697
    public function setCurlOptions($options)
698
    {
699
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
700 375
701 375
        $this->extracurlopts = $options;
702 32
        return $this;
703 32
    }
704
705
    /**
706
     * @param int $useCurlMode self::USE_CURL_ALWAYS, self::USE_CURL_AUTO or self::USE_CURL_NEVER
707
     * @return $this
708 375
     * @deprecated use setOption
709 375
     */
710 73
    public function setUseCurl($useCurlMode)
711
    {
712
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
713 375
714 375
        $this->use_curl = $useCurlMode;
715 32
        return $this;
716
    }
717
718 32
719 32
    /**
720 32
     * Set user-agent string that will be used by this client instance in http headers sent to the server.
721 32
     *
722 32
     * The default user agent string includes the name of this library and the version number.
723
     *
724
     * @param string $agentString
725
     * @return $this
726 32
     * @deprecated use setOption
727
     */
728
    public function setUserAgent($agentString)
729 343
    {
730 343
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
731 343
732 343
        $this->user_agent = $agentString;
733
        return $this;
734
    }
735
736 375
    /**
737 375
     * @param null|int $component allowed values: PHP_URL_SCHEME, PHP_URL_HOST, PHP_URL_PORT, PHP_URL_PATH
738 309
     * @return string|int Notes: the path component will include query string and fragment; NULL is a valid value for port
739 309
     *                    (in which case the default port for http/https will be used);
740 309
     * @throws ValueErrorException on unsupported component
741
     */
742
    public function getUrl($component = null)
743
    {
744
        if (is_int($component) || ctype_digit($component)) {
745
            switch ($component) {
746
                case PHP_URL_SCHEME:
747
                    return $this->method;
748
                case PHP_URL_HOST:
749
                    return $this->server;
750
                case PHP_URL_PORT:
751
                    return $this->port;
752
                case  PHP_URL_PATH:
753 309
                    return $this->path;
754
                case '':
755
756 309
                default:
757
                    throw new ValueErrorException("Unsupported component '$component'");
758
            }
759
        }
760 375
761 375
        $url = $this->method . '://' . $this->server;
762
        if ($this->port == 0 || ($this->port == 80 && in_array($this->method, array('http', 'http10', 'http11', 'h2c'))) ||
763
            ($this->port == 443 && in_array($this->method, array('https', 'h2')))) {
764
            return $url . $this->path;
765
        } else {
766 375
            return $url . ':' . $this->port . $this->path;
767 375
        }
768 375
    }
769 375
770 375
    /**
771 375
     * Send an xml-rpc request to the server.
772 375
     *
773 375
     * @param Request|Request[]|string $req The Request object, or an array of requests for using multicall, or the
774 375
     *                                      complete xml representation of a request.
775 375
     *                                      When sending an array of Request objects, the client will try to make use of
776 375
     *                                      a single 'system.multicall' xml-rpc method call to forward to the server all
777 375
     *                                      the requests in a single HTTP round trip, unless $this->no_multicall has
778
     *                                      been previously set to TRUE (see the multicall method below), in which case
779 375
     *                                      many consecutive xml-rpc requests will be sent. The method will return an
780 2
     *                                      array of Response objects in both cases.
781
     *                                      The third variant allows to build by hand (or any other means) a complete
782
     *                                      xml-rpc request message, and send it to the server. $req should be a string
783 375
     *                                      containing the complete xml representation of the request. It is e.g. useful
784 375
     *                                      when, for maximal speed of execution, the request is serialized into a
785 32
     *                                      string using the native php xml-rpc functions (see http://www.php.net/xmlrpc)
786
     * @param integer $timeout deprecated. Connection timeout, in seconds, If unspecified, the timeout set with setOption
787
     *                         will be used. If that is 0, a platform specific timeout will apply.
788
     *                         This timeout value is passed to fsockopen(). It is also used for detecting server
789
     *                         timeouts during communication (i.e. if the server does not send anything to the client
790
     *                         for $timeout seconds, the connection will be closed).
791 32
     * @param string $method deprecated. Use the same value in the constructor instead.
792
     *                       Valid values are 'http', 'http11', 'https', 'h2' and 'h2c'. If left empty,
793
     *                       the http protocol chosen during creation of the object will be used.
794 32
     *                       Use 'h2' to make the lib attempt to use http/2 over a secure connection, and 'h2c'
795
     *                       for http/2 without tls. Note that 'h2c' will not use the h2c 'upgrade' method, and be
796
     *                       thus incompatible with any server/proxy not supporting http/2. This is because POST
797 32
     *                       request are not compatible with h2c upgrade.
798
     * @return Response|Response[] Note that the client will always return a Response object, even if the call fails
799
     *
800 32
     * @todo allow throwing exceptions instead of returning responses in case of failed calls and/or Fault responses
801 32
     * @todo refactor: we now support many options besides connection timeout and http version to use. Why only privilege those?
802
     */
803
    public function send($req, $timeout = 0, $method = '')
804 375
    {
805
        if ($method !== '' || $timeout !== 0) {
806 375
            $this->logDeprecation("Using non-default values for arguments 'method' and 'timeout' when calling method " . __METHOD__ . ' is deprecated');
807 64
        }
808
809 311
        // if user does not specify http protocol, use native method of this client
810
        // (i.e. method set during call to constructor)
811
        if ($method == '') {
812 375
            $method = $this->method;
813 375
        }
814
815 375
        if ($timeout == 0) {
816 375
            $timeout = $this->timeout;
817 375
        }
818 374
819 367
        if (is_array($req)) {
820
            // $req is an array of Requests
821
            /// @todo switch to the new syntax for multicall
822 1
            return $this->multicall($req, $timeout, $method);
823
        } elseif (is_string($req)) {
824
            $n = new static::$requestClass('');
825
            $n->setPayload($req);
826
            $req = $n;
827 1
        }
828 1
829
        // where req is a Request
830 1
        $req->setDebug($this->debug);
831
832
        /// @todo we could be smarter about this and not force usage of curl for https if not present, as well as
833 374
        ///       use the presence of curl_extra_opts or socket_extra_opts as a hint
834
        $useCurl = ($this->use_curl == self::USE_CURL_ALWAYS) || ($this->use_curl == self::USE_CURL_AUTO && (
835
            in_array($method, array('https', 'http11', 'h2c', 'h2')) ||
836
            ($this->username != '' && $this->authtype != 1) ||
837
            ($this->proxy != '' && $this->proxy_user != '' && $this->proxy_authtype != 1)
838
        ));
839
840
        // BC - we go through sendPayloadCURL/sendPayloadSocket in case some subclass reimplemented those
841
        if ($useCurl) {
842
            $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

842
            $r = /** @scrutinizer ignore-deprecated */ $this->sendPayloadCURL(
Loading history...
843 374
                $req,
844
                $this->server,
845
                $this->port,
846
                $timeout,
847 374
                $this->username,
848 374
                $this->password,
849 374
                $this->authtype,
850
                $this->cert,
851 374
                $this->certpass,
852
                $this->cacert,
853 374
                $this->cacertdir,
854
                $this->proxy,
855
                $this->proxyport,
856
                $this->proxy_user,
857
                $this->proxy_pass,
858
                $this->proxy_authtype,
859
                // BC
860
                $method == 'http11' ? 'http' : $method,
861
                $this->keepalive,
862
                $this->key,
863
                $this->keypass,
864
                $this->sslversion
865
            );
866
        } else {
867
            $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

867
            $r = /** @scrutinizer ignore-deprecated */ $this->sendPayloadSocket(
Loading history...
868
                $req,
869
                $this->server,
870
                $this->port,
871
                $timeout,
872
                $this->username,
873
                $this->password,
874
                $this->authtype,
875
                $this->cert,
876
                $this->certpass,
877
                $this->cacert,
878
                $this->cacertdir,
879
                $this->proxy,
880
                $this->proxyport,
881
                $this->proxy_user,
882
                $this->proxy_pass,
883
                $this->proxy_authtype,
884
                $method,
885
                $this->key,
886 322
                $this->keypass,
887
                $this->sslversion
888
            );
889
        }
890
891 322
        return $r;
892
    }
893
894
    /**
895 322
     * @param Request $req
896
     * @param string $method
897 96
     * @param string $server
898 96
     * @param int $port
899
     * @param string $path
900
     * @param array $opts
901
     * @return Response
902
     */
903
    protected function sendViaSocket($req, $method, $server, $port, $path, $opts)
904 322
    {
905 322
        /// @todo log a warning if passed an unsupported method
906
907
        // Only create the payload if it was not created previously
908
        /// @todo what if the request's payload was created with a different encoding?
909
        ///       Also, if we do not call serialize(), the request will not set its content-type to have the charset declared
910 322
        $payload = $req->getPayload();
911
        if (empty($payload)) {
912
            $payload = $req->serialize($opts['request_charset_encoding']);
913
        }
914
915 322
        // Deflate request body and set appropriate request headers
916
        $encodingHdr = '';
917 322
        if ($opts['request_compression'] == 'gzip' || $opts['request_compression'] == 'deflate') {
918
            if ($opts['request_compression'] == 'gzip' && function_exists('gzencode')) {
919
                $a = @gzencode($payload);
920
                if ($a) {
921
                    $payload = $a;
922
                    $encodingHdr = "Content-Encoding: gzip\r\n";
923
                } else {
924
                    $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzencode failure in compressing request');
925
                }
926
            } else if (function_exists('gzcompress')) {
927
                $a = @gzcompress($payload);
928
                if ($a) {
929 322
                    $payload = $a;
930
                    $encodingHdr = "Content-Encoding: deflate\r\n";
931
                } else {
932 1
                    $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzcompress failure in compressing request');
933 1
                }
934 1
            } else {
935 1
                $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported by this PHP install');
936 1
            }
937
        } else {
938
            if ($opts['request_compression'] != '') {
939 322
                $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported');
940 160
            }
941
        }
942 322
943
        // thanks to Grant Rauscher
944 322
        $credentials = '';
945
        if ($opts['username'] != '') {
946
            if ($opts['authtype'] != 1) {
947
                $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported with HTTP 1.0');
948
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
949
                    PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': only Basic auth is supported with HTTP 1.0');
950 322
            }
951
            $credentials = 'Authorization: Basic ' . base64_encode($opts['username'] . ':' . $opts['password']) . "\r\n";
952
        }
953 323
954
        $acceptedEncoding = '';
955
        if (is_array($opts['accepted_compression']) && count($opts['accepted_compression'])) {
956
            $acceptedEncoding = 'Accept-Encoding: ' . implode(', ', $opts['accepted_compression']) . "\r\n";
957
        }
958 323
959 322
        if ($port == 0) {
960 226
            $port = ($method === 'https') ? 443 : 80;
961
        }
962 96
963
        $proxyCredentials = '';
964
        if ($opts['proxy']) {
965
            if ($opts['proxyport'] == 0) {
966
                $opts['proxyport'] = 8080;
967 323
            }
968 302
            $connectServer = $opts['proxy'];
969
            $connectPort = $opts['proxyport'];
970
            $transport = 'tcp';
971
            /// @todo check: should we not use https in some cases?
972 323
            $uri = 'http://' . $server . ':' . $port . $path;
973 323
            if ($opts['proxy_user'] != '') {
974 64
                if ($opts['proxy_authtype'] != 1) {
975 32
                    $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported with HTTP 1.0');
976 32
                    return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
977 32
                        PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': only Basic auth to proxy is supported with HTTP 1.0');
978 32
                }
979
                $proxyCredentials = 'Proxy-Authorization: Basic ' . base64_encode($opts['proxy_user'] . ':' .
980
                    $opts['proxy_pass']) . "\r\n";
981 32
            }
982 32
        } else {
983 32
            $connectServer = $server;
984 64
            $connectPort = $port;
985
            $transport = ($method === 'https') ? 'tls' : 'tcp';
986
            $uri = $path;
987
        }
988 259
989
        // Cookie generation, as per RFC6265
990
        // NB: the following code does not honour 'expires', 'path' and 'domain' cookie attributes set to client obj by the user...
991 323
        $cookieHeader = '';
992 323
        if (count($opts['cookies'])) {
993 64
            $version = '';
994
            foreach ($opts['cookies'] as $name => $cookie) {
995 259
                /// @todo should we sanitize the cookie value on behalf of the user? See setCookie comments
996 32
                $cookieHeader .= ' ' . $name . '=' . $cookie['value'] . ";";
997
            }
998
            $cookieHeader = 'Cookie:' . $version . substr($cookieHeader, 0, -1) . "\r\n";
999 227
        }
1000
1001
        // omit port if default
1002 323
        if (($port == 80 && in_array($method, array('http', 'http10'))) || ($port == 443 && $method == 'https')) {
1003 323
            $port = '';
1004 322
        } else {
1005
            $port = ':' . $port;
1006
        }
1007 56
1008
        $op = 'POST ' . $uri . " HTTP/1.0\r\n" .
1009
            'User-Agent: ' . $opts['user_agent'] . "\r\n" .
1010
            'Host: ' . $server . $port . "\r\n" .
1011 323
            $credentials .
1012
            $proxyCredentials .
1013 323
            $acceptedEncoding .
1014
            $encodingHdr .
1015
            'Accept-Charset: ' . implode(',', $opts['accepted_charset_encodings']) . "\r\n" .
1016
            $cookieHeader .
1017 323
            'Content-Type: ' . $req->getContentType() . "\r\nContent-Length: " .
1018
            strlen($payload) . "\r\n\r\n" .
1019 323
            $payload;
1020
1021 323
        if ($opts['debug'] > 1) {
1022
            $this->getLogger()->debug("---SENDING---\n$op\n---END---");
1023
        }
1024 323
1025
        $contextOptions = array();
1026
        if ($method == 'https') {
1027
            if ($opts['cert'] != '') {
1028 323
                $contextOptions['ssl']['local_cert'] = $opts['cert'];
1029
                if ($opts['certpass'] != '') {
1030
                    $contextOptions['ssl']['passphrase'] = $opts['certpass'];
1031 66
                }
1032 64
            }
1033
            if ($opts['cacert'] != '') {
1034 2
                $contextOptions['ssl']['cafile'] = $opts['cacert'];
1035
            }
1036
            if ($opts['cacertdir'] != '') {
1037
                $contextOptions['ssl']['capath'] = $opts['cacertdir'];
1038 323
            }
1039
            if ($opts['key'] != '') {
1040 323
                $contextOptions['ssl']['local_pk'] = $opts['key'];
1041 161
            }
1042
            $contextOptions['ssl']['verify_peer'] = $opts['verifypeer'];
1043
            $contextOptions['ssl']['verify_peer_name'] = $opts['verifypeer'];
1044 323
        }
1045 64
1046
        foreach ($opts['extracurlopts'] as $proto => $protoOpts) {
1047
            foreach ($protoOpts as $key => $val) {
1048
                $contextOptions[$proto][$key] = $val;
1049
            }
1050 323
        }
1051
1052 323
        $context = stream_context_create($contextOptions);
1053
1054 323
        if ($opts['timeout'] <= 0) {
1055 272
            $connectTimeout = ini_get('default_socket_timeout');
1056
        } else {
1057
            $connectTimeout = $opts['timeout'];
1058 323
        }
1059 323
1060 32
        $this->errno = 0;
1061 32
        $this->errstr = '';
1062 291
1063
        $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr, $connectTimeout,
1064
            STREAM_CLIENT_CONNECT, $context);
1065 291
        if ($fp) {
0 ignored issues
show
introduced by
$fp is of type false|resource, thus it always evaluated to false.
Loading history...
1066 32
            if ($opts['timeout'] > 0) {
1067 32
                stream_set_timeout($fp, $opts['timeout'], 0);
1068 259
            }
1069 32
        } else {
1070 32
            if ($this->errstr == '') {
1071
                $err = error_get_last();
1072
                $this->errstr = $err['message'];
1073 323
            }
1074 32
1075 32
            $this->errstr = 'Connect error: ' . $this->errstr;
1076 32
            $r = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr . ' (' . $this->errno . ')');
1077
1078
            return $r;
1079
        }
1080
1081
        if (!fputs($fp, $op, strlen($op))) {
1082 323
            fclose($fp);
1083
            $this->errstr = 'Write error';
1084 96
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr);
1085
        }
1086
1087
        // Close socket before parsing.
1088 96
        // It should yield slightly better execution times, and make easier recursive calls (e.g. to follow http redirects)
1089
        $ipd = '';
1090
        do {
1091
            // shall we check for $data === FALSE?
1092 96
            // as per the manual, it signals an error
1093
            $ipd .= fread($fp, 32768);
1094 96
        } while (!feof($fp));
1095
        fclose($fp);
1096
1097 96
        return $req->parseResponse($ipd, false, $opts['return_type']);
1098
    }
1099
1100
    /**
1101 96
     * Contributed by Justin Miller
1102
     * Requires curl to be built into PHP
1103
     * NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers!
1104
     *
1105 96
     * @param Request $req
1106
     * @param string $method
1107
     * @param string $server
1108
     * @param int $port
1109
     * @param string $path
1110 96
     * @param array $opts the keys/values match self::getOptions
1111
     * @return Response
1112 96
     *
1113
     * @todo the $path arg atm is ignored. What to do if it is != $this->path?
1114
     */
1115
    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

1115
    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...
1116 323
    {
1117 64
        if (!function_exists('curl_init')) {
1118
            $this->errstr = 'CURL unavailable on this install';
1119
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_curl'], PhpXmlRpc::$xmlrpcstr['no_curl']);
1120 64
        }
1121 64
        if ($method == 'https' || $method == 'h2') {
1122
            // q: what about installs where we get back a string, but curl is linked to other ssl libs than openssl?
1123
            if (($info = curl_version()) &&
1124
                ((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...
1125
            ) {
1126
                $this->errstr = 'SSL unavailable on this install';
1127
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_ssl'], PhpXmlRpc::$xmlrpcstr['no_ssl']);
1128
            }
1129
        }
1130
        if (($method == 'h2' && !defined('CURL_HTTP_VERSION_2_0')) ||
1131
            ($method == 'h2c' && !defined('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE'))) {
1132
            $this->errstr = 'HTTP/2 unavailable on this install';
1133 323
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_http2'], PhpXmlRpc::$xmlrpcstr['no_http2']);
1134 271
        }
1135 271
1136 271
        // BC - we go through prepareCurlHandle in case some subclass reimplemented it
1137
        $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

1137
        $curl = /** @scrutinizer ignore-deprecated */ $this->prepareCurlHandle($req, $server, $port, $opts['timeout'], $opts['username'], $opts['password'],
Loading history...
1138 271
            $opts['authtype'], $opts['cert'], $opts['certpass'], $opts['cacert'], $opts['cacertdir'], $opts['proxy'],
1139
            $opts['proxyport'], $opts['proxy_user'], $opts['proxy_pass'], $opts['proxy_authtype'], $method,
1140
            $opts['keepalive'], $opts['key'], $opts['keypass'], $opts['sslversion']);
1141 323
1142
        if (!$curl) {
1143
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] .
1144
                ': error during curl initialization. Check php error log for details');
1145 323
        }
1146
1147
        $result = curl_exec($curl);
1148
1149 323
        if ($opts['debug'] > 1) {
1150
            $message = "---CURL INFO---\n";
1151
            foreach (curl_getinfo($curl) as $name => $val) {
1152
                if (is_array($val)) {
1153
                    $val = implode("\n", $val);
1154
                }
1155
                $message .= $name . ': ' . $val . "\n";
1156
            }
1157
            $message .= '---END---';
1158
            $this->getLogger()->debug($message);
1159
        }
1160
1161
        if (!$result) {
1162
            /// @todo we should use a better check here - what if we get back '' or '0'?
1163
1164
            $this->errstr = 'no response';
1165
            $resp = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] .
1166
                ': ' . curl_error($curl));
1167
            curl_close($curl);
1168
            if ($opts['keepalive']) {
1169
                $this->xmlrpc_curl_handle = null;
1170
            }
1171
        } else {
1172
            if (!$opts['keepalive']) {
1173
                curl_close($curl);
1174 66
            }
1175
            $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

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

1683
        return /** @scrutinizer ignore-deprecated */ $this->sendPayloadSocket($req, $server, $port, $timeout, $username, $password, $authType, null, null,
Loading history...
1684
            null, null, $proxyHost, $proxyPort, $proxyUsername, $proxyPassword, $proxyAuthType, $method);
1685
    }
1686
1687
    /**
1688
     * @deprecated
1689
     *
1690
     * @param Request $req
1691
     * @param string $server
1692
     * @param int $port
1693
     * @param int $timeout
1694
     * @param string $username
1695
     * @param string $password
1696
     * @param int $authType
1697
     * @param string $cert
1698
     * @param string $certPass
1699
     * @param string $caCert
1700
     * @param string $caCertDir
1701
     * @param string $proxyHost
1702
     * @param int $proxyPort
1703
     * @param string $proxyUsername
1704
     * @param string $proxyPassword
1705
     * @param int $proxyAuthType
1706
     * @param bool $keepAlive
1707
     * @param string $key
1708
     * @param string $keyPass
1709
     * @param int $sslVersion
1710
     * @return Response
1711
     */
1712
    protected function sendPayloadHTTPS($req, $server, $port, $timeout = 0, $username = '', $password = '',
1713
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
1714
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $keepAlive = false, $key = '', $keyPass = '',
1715
        $sslVersion = 0)
1716
    {
1717
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
1718
1719
        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

1719
        return /** @scrutinizer ignore-deprecated */ $this->sendPayloadCURL($req, $server, $port, $timeout, $username,
Loading history...
1720
            $password, $authType, $cert, $certPass, $caCert, $caCertDir, $proxyHost, $proxyPort,
1721
            $proxyUsername, $proxyPassword, $proxyAuthType, 'https', $keepAlive, $key, $keyPass, $sslVersion);
1722
    }
1723
1724
    /**
1725
     * @deprecated
1726
     *
1727
     * @param Request $req
1728
     * @param string $server
1729
     * @param int $port
1730
     * @param int $timeout
1731
     * @param string $username
1732
     * @param string $password
1733
     * @param int $authType only value supported is 1
1734
     * @param string $cert
1735
     * @param string $certPass
1736
     * @param string $caCert
1737
     * @param string $caCertDir
1738
     * @param string $proxyHost
1739
     * @param int $proxyPort
1740
     * @param string $proxyUsername
1741
     * @param string $proxyPassword
1742
     * @param int $proxyAuthType only value supported is 1
1743
     * @param string $method 'http' (synonym for 'http10'), 'http10' or 'https'
1744
     * @param string $key
1745
     * @param string $keyPass @todo not implemented yet.
1746
     * @param int $sslVersion @todo not implemented yet. See http://php.net/manual/en/migration56.openssl.php
1747
     * @return Response
1748
     */
1749
    protected function sendPayloadSocket($req, $server, $port, $timeout = 0, $username = '', $password = '',
1750
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
1751
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'http', $key = '', $keyPass = '',
1752
        $sslVersion = 0)
1753
    {
1754
        $this->logDeprecationUnlessCalledBy('send');
1755
1756
        return $this->sendViaSocket($req, $method, $server, $port, $this->path, array(
1757
            'accepted_charset_encodings' => $this->accepted_charset_encodings,
1758
            'accepted_compression' => $this->accepted_compression,
1759
            'authtype' => $authType,
1760
            'cacert' => $caCert,
1761
            'cacertdir' => $caCertDir,
1762
            'cert' => $cert,
1763
            'certpass' => $certPass,
1764
            'cookies' => $this->cookies,
1765
            'debug' => $this->debug,
1766
            'extracurlopts' => $this->extracurlopts,
1767
            'extrasockopts' => $this->extrasockopts,
1768
            'keepalive' => $this->keepalive,
1769
            'key' => $key,
1770
            'keypass' => $keyPass,
1771
            'no_multicall' => $this->no_multicall,
1772
            'password' => $password,
1773
            'proxy' => $proxyHost,
1774
            'proxy_authtype' => $proxyAuthType,
1775
            'proxy_pass' => $proxyPassword,
1776
            'proxyport' => $proxyPort,
1777
            'proxy_user' => $proxyUsername,
1778
            'request_charset_encoding' => $this->request_charset_encoding,
1779
            'request_compression' => $this->request_compression,
1780
            'return_type' => $this->return_type,
1781
            'sslversion' => $sslVersion,
1782
            'timeout' => $timeout,
1783
            'username' => $username,
1784
            'user_agent' => $this->user_agent,
1785
            'use_curl' => $this->use_curl,
1786
            'verifyhost' => $this->verifyhost,
1787
            'verifypeer' => $this->verifypeer,
1788
        ));
1789
    }
1790
1791
    /**
1792
     * @deprecated
1793
     *
1794
     * @param Request $req
1795
     * @param string $server
1796
     * @param int $port
1797
     * @param int $timeout
1798
     * @param string $username
1799
     * @param string $password
1800
     * @param int $authType
1801
     * @param string $cert
1802
     * @param string $certPass
1803
     * @param string $caCert
1804
     * @param string $caCertDir
1805
     * @param string $proxyHost
1806
     * @param int $proxyPort
1807
     * @param string $proxyUsername
1808
     * @param string $proxyPassword
1809
     * @param int $proxyAuthType
1810
     * @param string $method 'http' (let curl decide), 'http10', 'http11', 'https', 'h2c' or 'h2'
1811
     * @param bool $keepAlive
1812
     * @param string $key
1813
     * @param string $keyPass
1814
     * @param int $sslVersion
1815
     * @return Response
1816
     */
1817
    protected function sendPayloadCURL($req, $server, $port, $timeout = 0, $username = '', $password = '',
1818
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
1819
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'https', $keepAlive = false, $key = '',
1820
        $keyPass = '', $sslVersion = 0)
1821
    {
1822
        $this->logDeprecationUnlessCalledBy('send');
1823
1824
        return $this->sendViaCURL($req, $method, $server, $port, $this->path, array(
1825
            'accepted_charset_encodings' => $this->accepted_charset_encodings,
1826
            'accepted_compression' => $this->accepted_compression,
1827
            'authtype' => $authType,
1828
            'cacert' => $caCert,
1829
            'cacertdir' => $caCertDir,
1830
            'cert' => $cert,
1831
            'certpass' => $certPass,
1832
            'cookies' => $this->cookies,
1833
            'debug' => $this->debug,
1834
            'extracurlopts' => $this->extracurlopts,
1835
            'extrasockopts' => $this->extrasockopts,
1836
            'keepalive' => $keepAlive,
1837
            'key' => $key,
1838
            'keypass' => $keyPass,
1839
            'no_multicall' => $this->no_multicall,
1840
            'password' => $password,
1841
            'proxy' => $proxyHost,
1842
            'proxy_authtype' => $proxyAuthType,
1843
            'proxy_pass' => $proxyPassword,
1844
            'proxyport' => $proxyPort,
1845
            'proxy_user' => $proxyUsername,
1846
            'request_charset_encoding' => $this->request_charset_encoding,
1847
            'request_compression' => $this->request_compression,
1848
            'return_type' => $this->return_type,
1849
            'sslversion' => $sslVersion,
1850
            'timeout' => $timeout,
1851
            'username' => $username,
1852
            'user_agent' => $this->user_agent,
1853
            'use_curl' => $this->use_curl,
1854
            'verifyhost' => $this->verifyhost,
1855
            'verifypeer' => $this->verifypeer,
1856
        ));
1857
    }
1858
1859
    /**
1860
     * @deprecated
1861
     *
1862
     * @param $req
1863
     * @param $server
1864
     * @param $port
1865
     * @param $timeout
1866
     * @param $username
1867
     * @param $password
1868
     * @param $authType
1869
     * @param $cert
1870
     * @param $certPass
1871
     * @param $caCert
1872
     * @param $caCertDir
1873
     * @param $proxyHost
1874
     * @param $proxyPort
1875
     * @param $proxyUsername
1876
     * @param $proxyPassword
1877
     * @param $proxyAuthType
1878
     * @param $method
1879
     * @param $keepAlive
1880
     * @param $key
1881
     * @param $keyPass
1882
     * @param $sslVersion
1883
     * @return false|\CurlHandle|resource
1884
     */
1885
    protected function prepareCurlHandle($req, $server, $port, $timeout = 0, $username = '', $password = '',
1886
         $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
1887
         $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'https', $keepAlive = false, $key = '',
1888
         $keyPass = '', $sslVersion = 0)
1889
    {
1890
        $this->logDeprecationUnlessCalledBy('sendViaCURL');
1891
1892
        return $this->createCURLHandle($req, $method, $server, $port, $this->path, array(
1893
            'accepted_charset_encodings' => $this->accepted_charset_encodings,
1894
            'accepted_compression' => $this->accepted_compression,
1895
            'authtype' => $authType,
1896
            'cacert' => $caCert,
1897
            'cacertdir' => $caCertDir,
1898
            'cert' => $cert,
1899
            'certpass' => $certPass,
1900
            'cookies' => $this->cookies,
1901
            'debug' => $this->debug,
1902
            'extracurlopts' => $this->extracurlopts,
1903
            'keepalive' => $keepAlive,
1904
            'key' => $key,
1905
            'keypass' => $keyPass,
1906
            'no_multicall' => $this->no_multicall,
1907
            'password' => $password,
1908
            'proxy' => $proxyHost,
1909
            'proxy_authtype' => $proxyAuthType,
1910
            'proxy_pass' => $proxyPassword,
1911
            'proxyport' => $proxyPort,
1912
            'proxy_user' => $proxyUsername,
1913
            'request_charset_encoding' => $this->request_charset_encoding,
1914
            'request_compression' => $this->request_compression,
1915
            'return_type' => $this->return_type,
1916
            'sslversion' => $sslVersion,
1917
            'timeout' => $timeout,
1918
            'username' => $username,
1919
            'user_agent' => $this->user_agent,
1920
            'use_curl' => $this->use_curl,
1921
            'verifyhost' => $this->verifyhost,
1922
            'verifypeer' => $this->verifypeer,
1923
        ));
1924
    }
1925
1926
    // we have to make this return by ref in order to allow calls such as `$resp->_cookies['name'] = ['value' => 'something'];`
1927
    public function &__get($name)
1928
    {
1929
        if (in_array($name, static::$options)) {
1930
            $this->logDeprecation('Getting property Client::' . $name . ' is deprecated');
1931
            return $this->$name;
1932
        }
1933
1934
        switch ($name) {
1935
            case 'errno':
1936
            case 'errstr':
1937
            case 'method':
1938
            case 'server':
1939
            case 'port':
1940
            case 'path':
1941
                $this->logDeprecation('Getting property Client::' . $name . ' is deprecated');
1942
                return $this->$name;
1943
            default:
1944
                /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout...
1945
                $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
1946
                trigger_error('Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
1947
                $result = null;
1948
                return $result;
1949
        }
1950
    }
1951
1952
    public function __set($name, $value)
1953
    {
1954
        if (in_array($name, static::$options)) {
1955
            $this->logDeprecation('Setting property Client::' . $name . ' is deprecated');
1956
            $this->$name = $value;
1957
            return;
1958
        }
1959
1960
        switch ($name) {
1961
            case 'errno':
1962
            case 'errstr':
1963
            case 'method':
1964
            case 'server':
1965
            case 'port':
1966
            case 'path':
1967
                $this->logDeprecation('Setting property Client::' . $name . ' is deprecated');
1968
                $this->$name = $value;
1969
                return;
1970
            default:
1971
                /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout...
1972
                $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
1973
                trigger_error('Undefined property via __set(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
1974
        }
1975
    }
1976
1977
    public function __isset($name)
1978
    {
1979
        if (in_array($name, static::$options)) {
1980
            $this->logDeprecation('Checking property Client::' . $name . ' is deprecated');
1981
            return isset($this->$name);
1982
        }
1983
1984
        switch ($name) {
1985
            case 'errno':
1986
            case 'errstr':
1987
            case 'method':
1988
            case 'server':
1989
            case 'port':
1990
            case 'path':
1991
                $this->logDeprecation('Checking property Client::' . $name . ' is deprecated');
1992
                return isset($this->$name);
1993
            default:
1994
                return false;
1995
        }
1996
    }
1997
1998
    public function __unset($name)
1999
    {
2000
        if (in_array($name, static::$options)) {
2001
            $this->logDeprecation('Unsetting property Client::' . $name . ' is deprecated');
2002
            unset($this->$name);
2003
            return;
2004
        }
2005
2006
        switch ($name) {
2007
            case 'errno':
2008
            case 'errstr':
2009
            case 'method':
2010
            case 'server':
2011
            case 'port':
2012
            case 'path':
2013
                $this->logDeprecation('Unsetting property Client::' . $name . ' is deprecated');
2014
                unset($this->$name);
2015
                return;
2016
            default:
2017
                /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout...
2018
                $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
2019
                trigger_error('Undefined property via __unset(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
2020
        }
2021
    }
2022
}
2023