Passed
Push — master ( bcc3fa...b1cc15 )
by Gaetano
17:19 queued 06:35
created

Client_for_ssl_proxy::setAcceptedCompression()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

Loading history...
1032
            $extraHeaders = implode("\r\n", $this->extra_headers) . "\r\n";
1033
        }
1034
1035
        // omit port if default
1036
        /// @todo add handling of http2, h2c in case they start being supported by fosckopen
1037
        if (($port == 80 && in_array($method, array('http', 'http10', 'http11'))) || ($port == 443 && $method == 'https')) {
1038
            $port = '';
1039
        } else {
1040
            $port = ':' . $port;
1041
        }
1042
1043
        $op = 'POST ' . $uri . " HTTP/1.0\r\n" .
1044
            'User-Agent: ' . $opts['user_agent'] . "\r\n" .
1045
            'Host: ' . $server . $port . "\r\n" .
1046
            $credentials .
1047
            $proxyCredentials .
1048
            $acceptedEncoding .
1049
            $encodingHdr .
1050
            'Accept-Charset: ' . implode(',', $opts['accepted_charset_encodings']) . "\r\n" .
1051
            $cookieHeader .
1052
            'Content-Type: ' . $req->getContentType() . "\r\n" .
1053
            $extraHeaders .
1054
            'Content-Length: ' . strlen($payload) . "\r\n\r\n" .
1055
            $payload;
1056
1057
        if ($opts['debug'] > 1) {
1058
            $this->getLogger()->debug("---SENDING---\n$op\n---END---");
1059
        }
1060
1061
        $contextOptions = array();
1062
        if ($method == 'https') {
1063
            if ($opts['cert'] != '') {
1064
                $contextOptions['ssl']['local_cert'] = $opts['cert'];
1065
                if ($opts['certpass'] != '') {
1066
                    $contextOptions['ssl']['passphrase'] = $opts['certpass'];
1067
                }
1068
            }
1069
            if ($opts['cacert'] != '') {
1070
                $contextOptions['ssl']['cafile'] = $opts['cacert'];
1071
            }
1072
            if ($opts['cacertdir'] != '') {
1073
                $contextOptions['ssl']['capath'] = $opts['cacertdir'];
1074
            }
1075
            if ($opts['key'] != '') {
1076
                $contextOptions['ssl']['local_pk'] = $opts['key'];
1077
            }
1078
            $contextOptions['ssl']['verify_peer'] = $opts['verifypeer'];
1079
            $contextOptions['ssl']['verify_peer_name'] = $opts['verifypeer'];
1080
1081
            if ($opts['sslversion'] != 0) {
1082
                /// @see https://www.php.net/manual/en/function.curl-setopt.php, https://www.php.net/manual/en/migration56.openssl.php
1083
                switch($opts['sslversion']) {
1084
                    /// @todo what does this map to? 1.0-1.3?
1085
                    //case 1: // TLSv1
1086
                    //    break;
1087
                    case 2: // SSLv2
1088
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_SSLv2_CLIENT;
1089
                        break;
1090
                    case 3: // SSLv3
1091
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_SSLv3_CLIENT;
1092
                        break;
1093
                    case 4: // TLSv1.0 - not always available
1094
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT;
1095
                        break;
1096
                    case 5: // TLSv1.1 - not always available
1097
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
1098
                        break;
1099
                    case 6: // TLSv1.2 - not always available
1100
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
1101
                        break;
1102
                    case 7: // TLSv1.3 - not always available
1103
                        if (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT')) {
1104
                            $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT;
1105
                        } else {
1106
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
1107
                                PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': TLS-1.3 only is supported with PHP 7.4 or later');
1108
                        }
1109
                        break;
1110
                    default:
1111
                        return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
1112
                            PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': Unsupported required TLS version');
1113
                }
1114
            }
1115
        }
1116
1117
        foreach ($opts['extrasockopts'] as $proto => $protoOpts) {
1118
            foreach ($protoOpts as $key => $val) {
1119
                $contextOptions[$proto][$key] = $val;
1120
            }
1121
        }
1122
1123
        $context = stream_context_create($contextOptions);
1124
1125
        if ($opts['timeout'] <= 0) {
1126
            $connectTimeout = ini_get('default_socket_timeout');
1127
        } else {
1128
            $connectTimeout = $opts['timeout'];
1129
        }
1130
1131
        $this->errno = 0;
1132
        $this->errstr = '';
1133
1134
        /// @todo using `error_get_last` does not give us very detailed messages for connections errors, eg. for ssl
1135
        ///       problems on php 5.6 we get 'Connect error: stream_socket_client(): unable to connect to tls://localhost:443 (Unknown error) (0)',
1136
        ///       versus the more detailed warnings 'PHP Warning:  stream_socket_client(): SSL operation failed with code 1. OpenSSL Error messages:
1137
        ///         error:0A0C0103:SSL routines::internal error in /home/docker/workspace/src/Client.php on line 1121
1138
        ///         PHP Warning:  stream_socket_client(): Failed to enable crypto in /home/docker/workspace/src/Client.php on line 1121
1139
        ///         PHP Warning:  stream_socket_client(): unable to connect to tls://localhost:443 (Unknown error) in /home/docker/workspace/src/Client.php on line 1121'
1140
        ///       This could be obviated by removing the `@` and capturing warnings via ob_start and co
1141
        $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr, $connectTimeout,
1142
            STREAM_CLIENT_CONNECT, $context);
1143
        if ($fp) {
0 ignored issues
show
introduced by
$fp is of type false|resource, thus it always evaluated to false.
Loading history...
1144
            if ($opts['timeout'] > 0) {
1145
                stream_set_timeout($fp, $opts['timeout'], 0);
1146
            }
1147
        } else {
1148
            if ($this->errstr == '') {
1149
                $err = error_get_last();
1150
                $this->errstr = $err['message'];
1151
            }
1152
1153
            $this->errstr = 'Connect error: ' . $this->errstr;
1154
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr . ' (' . $this->errno . ')');
1155
        }
1156
1157
$ap = $op;
1158
$ap = str_replace('Host: localhost', 'Host: localhost:443', $ap);
1159
$op = "CONNECT " . $uri . " HTTP/1.0\r\n" . 'Host: ' . $server . $port . "\r\n\r\n";
1160
echo "In fact sending: "; var_dump($op);
0 ignored issues
show
Security Debugging Code introduced by
var_dump($op) looks like debug code. Are you sure you do not want to remove it?
Loading history...
1161
1162
        if (!fputs($fp, $op, strlen($op))) {
1163
            fclose($fp);
1164
            $this->errstr = 'Write error';
1165
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr);
1166
        }
1167
1168
        $info = stream_get_meta_data($fp);
1169
        if ($info['timed_out']) {
1170
            fclose($fp);
1171
            $this->errstr = 'Write timeout';
1172
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr);
1173
        }
1174
1175
        // Close socket before parsing.
1176
        // It should yield slightly better execution times, and make easier recursive calls (e.g. to follow http redirects)
1177
        $ipd = '';
1178
        do {
1179
            // shall we check for $data === FALSE?
1180
            // as per the manual, it signals an error
1181
            $ipd .= fread($fp, 32768);
1182
1183
            $info = stream_get_meta_data($fp);
1184
            if ($info['timed_out']) {
1185
                fclose($fp);
1186
                $this->errstr = 'Read timeout';
1187
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr);
1188
            }
1189
1190
echo "got back: "; var_dump($ipd);
1191
if (true && $ipd === "HTTP/1.1 200 Connection established\r\n\r\n") {
1192
    $ap = str_replace('https://localhost:443', '', $ap);
1193
    //$ap = str_replace('Host: localhost', 'Host: localhost:443', $ap);
1194
echo "now sending: "; var_dump($ap);
1195
    fputs($fp, $ap, strlen($ap));
1196
}
1197
        } while (!feof($fp));
1198
        fclose($fp);
1199
var_dump($ipd);
1200
        return $req->parseResponse($ipd, false, $opts['return_type']);
1201
    }
1202
1203
    /**
1204
     * Contributed by Justin Miller
1205
     * Requires curl to be built into PHP
1206
     * NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers!
1207
     *
1208
     * @param Request $req
1209
     * @param string $method
1210
     * @param string $server
1211
     * @param int $port
1212
     * @param string $path
1213
     * @param array $opts the keys/values match self::getOptions
1214
     * @return Response
1215
     *
1216
     * @todo the $path arg atm is ignored. What to do if it is != $this->path?
1217
     */
1218
    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

1218
    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...
1219
    {
1220
        if (!function_exists('curl_init')) {
1221
            $this->errstr = 'CURL unavailable on this install';
1222
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_curl'], PhpXmlRpc::$xmlrpcstr['no_curl']);
1223
        }
1224
        if ($method == 'https' || $method == 'h2') {
1225
            // q: what about installs where we get back a string, but curl is linked to other ssl libs than openssl?
1226
            if (($info = curl_version()) &&
1227
                ((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...
1228
            ) {
1229
                $this->errstr = 'SSL unavailable on this install';
1230
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_ssl'], PhpXmlRpc::$xmlrpcstr['no_ssl']);
1231
            }
1232
        }
1233
        if (($method == 'h2' && !defined('CURL_HTTP_VERSION_2_0')) ||
1234
            ($method == 'h2c' && !defined('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE'))) {
1235
            $this->errstr = 'HTTP/2 unavailable on this install';
1236
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_http2'], PhpXmlRpc::$xmlrpcstr['no_http2']);
1237
        }
1238
1239
        // BC - we go through prepareCurlHandle in case some subclass reimplemented it
1240
        $curl = $this->prepareCurlHandle($req, $server, $port, $opts['timeout'], $opts['username'], $opts['password'],
0 ignored issues
show
Deprecated Code introduced by
The function PhpXmlRpc\Client_for_ssl...xy::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

1240
        $curl = /** @scrutinizer ignore-deprecated */ $this->prepareCurlHandle($req, $server, $port, $opts['timeout'], $opts['username'], $opts['password'],
Loading history...
1241
            $opts['authtype'], $opts['cert'], $opts['certpass'], $opts['cacert'], $opts['cacertdir'], $opts['proxy'],
1242
            $opts['proxyport'], $opts['proxy_user'], $opts['proxy_pass'], $opts['proxy_authtype'], $method,
1243
            $opts['keepalive'], $opts['key'], $opts['keypass'], $opts['sslversion']);
1244
1245
        if (!$curl) {
1246
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] .
1247
                ': error during curl initialization. Check php error log for details');
1248
        }
1249
1250
        $result = curl_exec($curl);
1251
1252
        if ($opts['debug'] > 1) {
1253
            $message = "---CURL INFO---\n";
1254
            foreach (curl_getinfo($curl) as $name => $val) {
1255
                if (is_array($val)) {
1256
                    $val = implode("\n", $val);
1257
                }
1258
                $message .= $name . ': ' . $val . "\n";
1259
            }
1260
            $message .= '---END---';
1261
            $this->getLogger()->debug($message);
1262
        }
1263
1264
        if (!$result) {
1265
            /// @todo we should use a better check here - what if we get back '' or '0'?
1266
1267
            $this->errstr = 'no response';
1268
            $resp = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] .
1269
                ': ' . curl_error($curl));
1270
            curl_close($curl);
1271
            if ($opts['keepalive']) {
1272
                $this->xmlrpc_curl_handle = null;
1273
            }
1274
        } else {
1275
            if (!$opts['keepalive']) {
1276
                curl_close($curl);
1277
            }
1278
            $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

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

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

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

Loading history...
1411
            $headers = array_merge($headers, $this->extra_headers);
1412
        }
1413
1414
        // Fix the HTTP/1.1 417 Expectation Failed Bug (curl by default adds a 'Expect: 100-continue' header when POST
1415
        // size exceeds 1025 bytes, apparently)
1416
        $headers[] = 'Expect:';
1417
1418
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
1419
        // previous note: "timeout is borked" (on some old php/curl versions? It seems to work on 8.1. Maybe the issue
1420
        // has to do with dns resolution...)
1421
        if ($opts['timeout']) {
1422
            curl_setopt($curl, CURLOPT_TIMEOUT, $opts['timeout']);
1423
        }
1424
1425
        switch ($method) {
1426
            case 'http10':
1427
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
1428
                break;
1429
            case 'http11':
1430
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
1431
                break;
1432
            case 'h2c':
1433
                if (defined('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE')) {
1434
                    curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE);
1435
                } else {
1436
                    $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. HTTP2 is not supported by the current PHP/curl install');
1437
                    curl_close($curl);
1438
                    return false;
1439
                }
1440
                break;
1441
            case 'h2':
1442
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
1443
                break;
1444
        }
1445
1446
        if ($opts['username'] && $opts['password']) {
1447
            curl_setopt($curl, CURLOPT_USERPWD, $opts['username'] . ':' . $opts['password']);
1448
            if (defined('CURLOPT_HTTPAUTH')) {
1449
                curl_setopt($curl, CURLOPT_HTTPAUTH, $opts['authtype']);
1450
            } elseif ($opts['authtype'] != 1) {
1451
                $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported by the current PHP/curl install');
1452
                curl_close($curl);
1453
                return false;
1454
            }
1455
        }
1456
1457
        // note: h2c is http2 without the https. No need to have it in this IF
1458
        if ($method == 'https' || $method == 'h2') {
1459
            // set cert file
1460
            if ($opts['cert']) {
1461
                curl_setopt($curl, CURLOPT_SSLCERT, $opts['cert']);
1462
            }
1463
            // set cert password
1464
            if ($opts['certpass']) {
1465
                curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $opts['certpass']);
1466
            }
1467
            // whether to verify remote host's cert
1468
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $opts['verifypeer']);
1469
            // set ca certificates file/dir
1470
            if ($opts['cacert']) {
1471
                curl_setopt($curl, CURLOPT_CAINFO, $opts['cacert']);
1472
            }
1473
            if ($opts['cacertdir']) {
1474
                curl_setopt($curl, CURLOPT_CAPATH, $opts['cacertdir']);
1475
            }
1476
            // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1477
            if ($opts['key']) {
1478
                curl_setopt($curl, CURLOPT_SSLKEY, $opts['key']);
1479
            }
1480
            // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1481
            if ($opts['keypass']) {
1482
                curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $opts['keypass']);
1483
            }
1484
            // whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that
1485
            // it matches the hostname used
1486
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $opts['verifyhost']);
1487
            // allow usage of different SSL versions
1488
            curl_setopt($curl, CURLOPT_SSLVERSION, $opts['sslversion']);
1489
        }
1490
1491
        // proxy info
1492
        if ($opts['proxy']) {
1493
            if ($opts['proxyport'] == 0) {
1494
                $opts['proxyport'] = 8080; // NB: even for HTTPS, local connection is on port 8080
1495
            }
1496
            curl_setopt($curl, CURLOPT_PROXY, $opts['proxy'] . ':' . $opts['proxyport']);
1497
            if ($opts['proxy_user']) {
1498
                curl_setopt($curl, CURLOPT_PROXYUSERPWD, $opts['proxy_user'] . ':' . $opts['proxy_pass']);
1499
                if (defined('CURLOPT_PROXYAUTH')) {
1500
                    curl_setopt($curl, CURLOPT_PROXYAUTH, $opts['proxy_authtype']);
1501
                } elseif ($opts['proxy_authtype'] != 1) {
1502
                    $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported by the current PHP/curl install');
1503
                    curl_close($curl);
1504
                    return false;
1505
                }
1506
            }
1507
        }
1508
1509
        // NB: should we build cookie http headers by hand rather than let CURL do it?
1510
        // NB: the following code does not honour 'expires', 'path' and 'domain' cookie attributes set to client obj by the user...
1511
        if (count($opts['cookies'])) {
1512
            $cookieHeader = '';
1513
            foreach ($opts['cookies'] as $name => $cookie) {
1514
                $cookieHeader .= $name . '=' . $cookie['value'] . '; ';
1515
            }
1516
            curl_setopt($curl, CURLOPT_COOKIE, substr($cookieHeader, 0, -2));
1517
        }
1518
1519
        foreach ($opts['extracurlopts'] as $opt => $val) {
1520
            curl_setopt($curl, $opt, $val);
1521
        }
1522
1523
        if ($opts['debug'] > 1) {
1524
            $this->getLogger()->debug("---SENDING---\n$payload\n---END---");
1525
        }
1526
1527
        return $curl;
1528
    }
1529
1530
    /**
1531
     * Send an array of requests and return an array of responses.
1532
     *
1533
     * Unless $this->no_multicall has been set to true, it will try first to use one single xml-rpc call to server method
1534
     * system.multicall, and revert to sending many successive calls in case of failure.
1535
     * This failure is also stored in $this->no_multicall for subsequent calls.
1536
     * Unfortunately, there is no server error code universally used to denote the fact that multicall is unsupported,
1537
     * so there is no way to reliably distinguish between that and a temporary failure.
1538
     * If you are sure that server supports multicall and do not want to fallback to using many single calls, set the
1539
     * 2np parameter to FALSE.
1540
     *
1541
     * NB: trying to shoehorn extra functionality into existing syntax has resulted
1542
     * in pretty much convoluted code...
1543
     *
1544
     * @param Request[] $reqs an array of Request objects
1545
     * @param bool $noFallback When true, upon receiving an error during multicall, multiple single calls will not be
1546
     *                         attempted.
1547
     *                         Deprecated alternative, was: int - "connection timeout (in seconds). See the details in the
1548
     *                         docs for the send() method". Please use setOption instead to set a timeout
1549
     * @param string $method deprecated. Was: "the http protocol variant to be used. See the details in the docs for the send() method."
1550
     *                       Please use the constructor to set an http protocol variant.
1551
     * @param boolean $fallback deprecated. Was: "when true, upon receiving an error during multicall, multiple single
1552
     *                          calls will be attempted"
1553
     * @return Response[]
1554
     */
1555
    public function multicall($reqs, $timeout = 0, $method = '', $fallback = true)
1556
    {
1557
        // BC
1558
        if (is_bool($timeout) && $fallback === true) {
1559
            $fallback = !$timeout;
1560
            $timeout = 0;
1561
        }
1562
1563
        if ($method == '') {
1564
            $method = $this->method;
1565
        }
1566
1567
        if (!$this->no_multicall) {
1568
            $results = $this->_try_multicall($reqs, $timeout, $method);
1569
            /// @todo how to handle the case of $this->return_type = xml?
1570
            if (is_array($results)) {
1571
                // System.multicall succeeded
1572
                return $results;
1573
            } else {
1574
                // either system.multicall is unsupported by server, or the call failed for some other reason.
1575
                // Feature creep: is there a way to tell apart unsupported multicall from other faults?
1576
                if ($fallback) {
1577
                    // Don't try it next time...
1578
                    $this->no_multicall = true;
1579
                } else {
1580
                    $result = $results;
1581
                }
1582
            }
1583
        } else {
1584
            // override fallback, in case careless user tries to do two
1585
            // opposite things at the same time
1586
            $fallback = true;
1587
        }
1588
1589
        $results = array();
1590
        if ($fallback) {
1591
            // system.multicall is (probably) unsupported by server: emulate multicall via multiple requests
1592
            /// @todo use curl multi_ functions to make this quicker (see the implementation in the parallel.php demo)
1593
            foreach ($reqs as $req) {
1594
                $results[] = $this->send($req, $timeout, $method);
1595
            }
1596
        } else {
1597
            // user does NOT want to fallback on many single calls: since we should always return an array of responses,
1598
            // we return an array with the same error repeated n times
1599
            foreach ($reqs as $req) {
1600
                $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...
1601
            }
1602
        }
1603
1604
        return $results;
1605
    }
1606
1607
    /**
1608
     * Attempt to boxcar $reqs via system.multicall.
1609
     *
1610
     * @param Request[] $reqs
1611
     * @param int $timeout
1612
     * @param string $method
1613
     * @return Response[]|Response a single Response when the call returned a fault / does not conform to what we expect
1614
     *                             from a multicall response
1615
     */
1616
    private function _try_multicall($reqs, $timeout, $method)
1617
    {
1618
        // Construct multicall request
1619
        $calls = array();
1620
        foreach ($reqs as $req) {
1621
            $call['methodName'] = new Value($req->method(), 'string');
1622
            $numParams = $req->getNumParams();
1623
            $params = array();
1624
            for ($i = 0; $i < $numParams; $i++) {
1625
                $params[$i] = $req->getParam($i);
1626
            }
1627
            $call['params'] = new Value($params, 'array');
1628
            $calls[] = new Value($call, 'struct');
1629
        }
1630
        $multiCall = new static::$requestClass('system.multicall');
1631
        $multiCall->addParam(new Value($calls, 'array'));
1632
1633
        // Attempt RPC call
1634
        $result = $this->send($multiCall, $timeout, $method);
1635
1636
        if ($result->faultCode() != 0) {
1637
            // call to system.multicall failed
1638
            return $result;
1639
        }
1640
1641
        // Unpack responses.
1642
        $rets = $result->value();
1643
        $response = array();
1644
1645
        if ($this->return_type == 'xml') {
1646
            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...
1647
                $response[] = new static::$responseClass($rets, 0, '', 'xml', $result->httpResponse());
1648
            }
1649
1650
        } elseif ($this->return_type == 'phpvals') {
1651
            if (!is_array($rets)) {
1652
                // bad return type from system.multicall
1653
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1654
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': not an array', 'phpvals', $result->httpResponse());
1655
            }
1656
            $numRets = count($rets);
1657
            if ($numRets != count($reqs)) {
1658
                // wrong number of return values.
1659
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1660
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': incorrect number of responses', 'phpvals',
1661
                    $result->httpResponse());
1662
            }
1663
1664
            for ($i = 0; $i < $numRets; $i++) {
1665
                $val = $rets[$i];
1666
                if (!is_array($val)) {
1667
                    return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1668
                        PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array or struct",
1669
                        'phpvals', $result->httpResponse());
1670
                }
1671
                switch (count($val)) {
1672
                    case 1:
1673
                        if (!isset($val[0])) {
1674
                            // Bad value
1675
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1676
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has no value",
1677
                                'phpvals', $result->httpResponse());
1678
                        }
1679
                        // Normal return value
1680
                        $response[$i] = new static::$responseClass($val[0], 0, '', 'phpvals', $result->httpResponse());
1681
                        break;
1682
                    case 2:
1683
                        /// @todo remove usage of @: it is apparently quite slow
1684
                        $code = @$val['faultCode'];
1685
                        if (!is_int($code)) {
1686
                            /// @todo should we check that it is != 0?
1687
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1688
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1689
                                'phpvals', $result->httpResponse());
1690
                        }
1691
                        $str = @$val['faultString'];
1692
                        if (!is_string($str)) {
1693
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1694
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no FaultString",
1695
                                'phpvals', $result->httpResponse());
1696
                        }
1697
                        $response[$i] = new static::$responseClass(0, $code, $str, 'phpvals', $result->httpResponse());
1698
                        break;
1699
                    default:
1700
                        return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1701
                            PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1702
                            'phpvals', $result->httpResponse());
1703
                }
1704
            }
1705
1706
        } else {
1707
            // return type == 'xmlrpcvals'
1708
            if ($rets->kindOf() != 'array') {
1709
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1710
                    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 1620. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
1711
                    $result->httpResponse());
1712
            }
1713
            $numRets = $rets->count();
1714
            if ($numRets != count($reqs)) {
1715
                // wrong number of return values.
1716
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1717
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': incorrect number of responses', 'xmlrpcvals',
1718
                    $result->httpResponse());
1719
            }
1720
1721
            foreach ($rets as $i => $val) {
1722
                switch ($val->kindOf()) {
1723
                    case 'array':
1724
                        if ($val->count() != 1) {
1725
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1726
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1727
                                'phpvals', $result->httpResponse());
1728
                        }
1729
                        // Normal return value
1730
                        $response[] = new static::$responseClass($val[0], 0, '', 'xmlrpcvals', $result->httpResponse());
1731
                        break;
1732
                    case 'struct':
1733
                        if ($val->count() != 2) {
1734
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1735
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1736
                                'phpvals', $result->httpResponse());
1737
                        }
1738
                        /** @var Value $code */
1739
                        $code = $val['faultCode'];
1740
                        if ($code->kindOf() != 'scalar' || $code->scalarTyp() != 'int') {
1741
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1742
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1743
                                'xmlrpcvals', $result->httpResponse());
1744
                        }
1745
                        /** @var Value $str */
1746
                        $str = $val['faultString'];
1747
                        if ($str->kindOf() != 'scalar' || $str->scalarTyp() != 'string') {
1748
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1749
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1750
                                'xmlrpcvals', $result->httpResponse());
1751
                        }
1752
                        $response[] = new static::$responseClass(0, $code->scalarVal(), $str->scalarVal(), 'xmlrpcvals', $result->httpResponse());
1753
                        break;
1754
                    default:
1755
                        return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1756
                            PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array or struct",
1757
                            'xmlrpcvals', $result->httpResponse());
1758
                }
1759
            }
1760
        }
1761
1762
        return $response;
1763
    }
1764
1765
    // *** BC layer ***
1766
1767
    /**
1768
     * NB: always goes via socket, never curl
1769
     *
1770
     * @deprecated
1771
     *
1772
     * @param Request $req
1773
     * @param string $server
1774
     * @param int $port
1775
     * @param int $timeout
1776
     * @param string $username
1777
     * @param string $password
1778
     * @param int $authType
1779
     * @param string $proxyHost
1780
     * @param int $proxyPort
1781
     * @param string $proxyUsername
1782
     * @param string $proxyPassword
1783
     * @param int $proxyAuthType
1784
     * @param string $method
1785
     * @return Response
1786
     */
1787
    protected function sendPayloadHTTP10($req, $server, $port, $timeout = 0, $username = '', $password = '',
1788
        $authType = 1, $proxyHost = '', $proxyPort = 0, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1,
1789
        $method = 'http')
1790
    {
1791
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
1792
1793
        return $this->sendPayloadSocket($req, $server, $port, $timeout, $username, $password, $authType, null, null,
0 ignored issues
show
Deprecated Code introduced by
The function PhpXmlRpc\Client_for_ssl...xy::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

1793
        return /** @scrutinizer ignore-deprecated */ $this->sendPayloadSocket($req, $server, $port, $timeout, $username, $password, $authType, null, null,
Loading history...
1794
            null, null, $proxyHost, $proxyPort, $proxyUsername, $proxyPassword, $proxyAuthType, $method);
1795
    }
1796
1797
    /**
1798
     * NB: always goes via curl, never socket
1799
     *
1800
     * @deprecated
1801
     *
1802
     * @param Request $req
1803
     * @param string $server
1804
     * @param int $port
1805
     * @param int $timeout
1806
     * @param string $username
1807
     * @param string $password
1808
     * @param int $authType
1809
     * @param string $cert
1810
     * @param string $certPass
1811
     * @param string $caCert
1812
     * @param string $caCertDir
1813
     * @param string $proxyHost
1814
     * @param int $proxyPort
1815
     * @param string $proxyUsername
1816
     * @param string $proxyPassword
1817
     * @param int $proxyAuthType
1818
     * @param bool $keepAlive
1819
     * @param string $key
1820
     * @param string $keyPass
1821
     * @param int $sslVersion
1822
     * @return Response
1823
     */
1824
    protected function sendPayloadHTTPS($req, $server, $port, $timeout = 0, $username = '', $password = '',
1825
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
1826
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $keepAlive = false, $key = '', $keyPass = '',
1827
        $sslVersion = 0)
1828
    {
1829
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
1830
1831
        return $this->sendPayloadCURL($req, $server, $port, $timeout, $username,
0 ignored issues
show
Deprecated Code introduced by
The function PhpXmlRpc\Client_for_ssl_proxy::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

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