Passed
Push — master ( 24898d...721548 )
by Gaetano
08:06
created

Client   F

Complexity

Total Complexity 274

Size/Duplication

Total Lines 1835
Duplicated Lines 0 %

Test Coverage

Coverage 74.46%

Importance

Changes 12
Bugs 2 Features 1
Metric Value
eloc 820
dl 0
loc 1835
ccs 382
cts 513
cp 0.7446
rs 1.78
c 12
b 2
f 1
wmc 274

29 Methods

Rating   Name   Duplication   Size   Complexity  
D setOption() 0 98 31
D getOption() 0 65 31
F __construct() 0 60 21
A setOptions() 0 7 2
A getOptions() 0 7 2
A setUseCurl() 0 4 1
F sendPayloadSocket() 0 181 38
A setCertificate() 0 5 1
A setSSLVerifyPeer() 0 4 1
A setCredentials() 0 6 1
F prepareCurlHandle() 0 212 53
A setAcceptedCompression() 0 10 3
A setDebug() 0 4 1
C send() 0 86 11
A setKey() 0 5 1
A sendPayloadHTTPS() 0 10 1
D sendPayloadCURL() 0 71 22
A setProxy() 0 8 1
F _try_multicall() 0 147 27
A setSSLVersion() 0 4 1
A getUrl() 0 8 5
A setSSLVerifyHost() 0 4 1
B multicall() 0 44 8
A sendPayloadHTTP10() 0 8 1
A setCaCertificate() 0 8 2
A setCookie() 0 12 4
A setRequestCompression() 0 4 1
A setCurlOptions() 0 4 1
A setUserAgent() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Client often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Client, and based on these observations, apply Extract Interface, too.

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

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

910
            return $url . ':' . /** @scrutinizer ignore-type */ $this->port . $this->path;
Loading history...
911
        }
912
    }
913
914
    /**
915 322
     * Send an xml-rpc request to the server.
916
     *
917 322
     * @param Request|Request[]|string $req The Request object, or an array of requests for using multicall, or the
918
     *                                      complete xml representation of a request.
919
     *                                      When sending an array of Request objects, the client will try to make use of
920
     *                                      a single 'system.multicall' xml-rpc method call to forward to the server all
921
     *                                      the requests in a single HTTP round trip, unless $this->no_multicall has
922
     *                                      been previously set to TRUE (see the multicall method below), in which case
923
     *                                      many consecutive xml-rpc requests will be sent. The method will return an
924
     *                                      array of Response objects in both cases.
925
     *                                      The third variant allows to build by hand (or any other means) a complete
926
     *                                      xml-rpc request message, and send it to the server. $req should be a string
927
     *                                      containing the complete xml representation of the request. It is e.g. useful
928
     *                                      when, for maximal speed of execution, the request is serialized into a
929 322
     *                                      string using the native php xml-rpc functions (see http://www.php.net/xmlrpc)
930
     * @param integer $timeout deprecated. Connection timeout, in seconds, If unspecified, the timeout set with setOption
931
     *                         will be used. If that is 0, a platform specific timeout will apply.
932 1
     *                         This timeout value is passed to fsockopen(). It is also used for detecting server
933 1
     *                         timeouts during communication (i.e. if the server does not send anything to the client
934 1
     *                         for $timeout seconds, the connection will be closed).
935 1
     * @param string $method deprecated. Use the same value in the constructor instead.
936 1
     *                       Valid values are 'http', 'http11', 'https', 'h2' and 'h2c'. If left empty,
937
     *                       the http protocol chosen during creation of the object will be used.
938
     *                       Use 'h2' to make the lib attempt to use http/2 over a secure connection, and 'h2c'
939 322
     *                       for http/2 without tls. Note that 'h2c' will not use the h2c 'upgrade' method, and be
940 160
     *                       thus incompatible with any server/proxy not supporting http/2. This is because POST
941
     *                       request are not compatible with h2c upgrade.
942 322
     * @return Response|Response[] Note that the client will always return a Response object, even if the call fails
943
     *
944 322
     * @todo allow throwing exceptions instead of returning responses in case of failed calls and/or Fault responses
945
     * @todo refactor: we now support many options besides connection timeout and http version to use. Why only privilege those?
946
     */
947
    public function send($req, $timeout = 0, $method = '')
948
    {
949
        if ($method !== '' || $timeout !== 0) {
950 322
            $this->logDeprecation("Using non-default values for arguments 'method' and 'timeout' when calling method " . __METHOD__ . ' is deprecated');
951
        }
952
953 323
        // if user does not specify http protocol, use native method of this client
954
        // (i.e. method set during call to constructor)
955
        if ($method == '') {
956
            $method = $this->method;
957
        }
958 323
959 322
        if ($timeout == 0) {
960 226
            $timeout = $this->timeout;
961
        }
962 96
963
        if (is_array($req)) {
964
            // $req is an array of Requests
965
            $r = $this->multicall($req, $timeout, $method);
966
967 323
            return $r;
968 302
        } elseif (is_string($req)) {
969
            $n = new static::$requestClass('');
970
            $n->payload = $req;
971
            $req = $n;
972 323
        }
973 323
974 64
        // where req is a Request
975 32
        $req->setDebug($this->debug);
976 32
977 32
        /// @todo we could be smarter about this and force usage of curl in scenarios where it is both available and
978 32
        ///       needed, such as digest or ntlm auth. Do not attempt to use it for https if not present
979
        $useCurl = ($this->use_curl == self::USE_CURL_ALWAYS) || ($this->use_curl == self::USE_CURL_AUTO &&
980
            (in_array($method, array('https', 'http11', 'h2c', 'h2'))));
981 32
982 32
        if ($useCurl) {
983 32
            $r = $this->sendPayloadCURL(
984 64
                $req,
985
                $this->server,
986
                $this->port,
987
                $timeout,
988 259
                $this->username,
989
                $this->password,
990
                $this->authtype,
991 323
                $this->cert,
992 323
                $this->certpass,
993 64
                $this->cacert,
994
                $this->cacertdir,
995 259
                $this->proxy,
996 32
                $this->proxyport,
997
                $this->proxy_user,
998
                $this->proxy_pass,
999 227
                $this->proxy_authtype,
1000
                // bc
1001
                $method == 'http11' ? 'http' : $method,
1002 323
                $this->keepalive,
1003 323
                $this->key,
1004 322
                $this->keypass,
1005
                $this->sslversion
1006
            );
1007 56
        } else {
1008
            $r = $this->sendPayloadSocket(
1009
                $req,
1010
                $this->server,
1011 323
                $this->port,
1012
                $timeout,
1013 323
                $this->username,
1014
                $this->password,
1015
                $this->authtype,
1016
                $this->cert,
1017 323
                $this->certpass,
1018
                $this->cacert,
1019 323
                $this->cacertdir,
1020
                $this->proxy,
1021 323
                $this->proxyport,
1022
                $this->proxy_user,
1023
                $this->proxy_pass,
1024 323
                $this->proxy_authtype,
1025
                $method,
1026
                $this->key,
1027
                $this->keypass,
1028 323
                $this->sslversion
1029
            );
1030
        }
1031 66
1032 64
        return $r;
1033
    }
1034 2
1035
    /**
1036
     * @deprecated
1037
     *
1038 323
     * @param Request $req
1039
     * @param string $server
1040 323
     * @param int $port
1041 161
     * @param int $timeout
1042
     * @param string $username
1043
     * @param string $password
1044 323
     * @param int $authType
1045 64
     * @param string $proxyHost
1046
     * @param int $proxyPort
1047
     * @param string $proxyUsername
1048
     * @param string $proxyPassword
1049
     * @param int $proxyAuthType
1050 323
     * @param string $method
1051
     * @return Response
1052 323
     */
1053
    protected function sendPayloadHTTP10($req, $server, $port, $timeout = 0, $username = '', $password = '',
1054 323
        $authType = 1, $proxyHost = '', $proxyPort = 0, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1,
1055 272
        $method='http')
1056
    {
1057
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
1058 323
1059 323
        return $this->sendPayloadSocket($req, $server, $port, $timeout, $username, $password, $authType, null, null,
1060 32
            null, null, $proxyHost, $proxyPort, $proxyUsername, $proxyPassword, $proxyAuthType, $method);
1061 32
    }
1062 291
1063
    /**
1064
     * @deprecated
1065 291
     *
1066 32
     * @param Request $req
1067 32
     * @param string $server
1068 259
     * @param int $port
1069 32
     * @param int $timeout
1070 32
     * @param string $username
1071
     * @param string $password
1072
     * @param int $authType
1073 323
     * @param string $cert
1074 32
     * @param string $certPass
1075 32
     * @param string $caCert
1076 32
     * @param string $caCertDir
1077
     * @param string $proxyHost
1078
     * @param int $proxyPort
1079
     * @param string $proxyUsername
1080
     * @param string $proxyPassword
1081
     * @param int $proxyAuthType
1082 323
     * @param bool $keepAlive
1083
     * @param string $key
1084 96
     * @param string $keyPass
1085
     * @param int $sslVersion
1086
     * @return Response
1087
     */
1088 96
    protected function sendPayloadHTTPS($req, $server, $port, $timeout = 0, $username = '',  $password = '',
1089
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
1090
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $keepAlive = false, $key = '', $keyPass = '',
1091
        $sslVersion = 0)
1092 96
    {
1093
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
1094 96
1095
        return $this->sendPayloadCURL($req, $server, $port, $timeout, $username,
1096
            $password, $authType, $cert, $certPass, $caCert, $caCertDir, $proxyHost, $proxyPort,
1097 96
            $proxyUsername, $proxyPassword, $proxyAuthType, 'https', $keepAlive, $key, $keyPass, $sslVersion);
1098
    }
1099
1100
    /**
1101 96
     * @param Request $req
1102
     * @param string $server
1103
     * @param int $port
1104
     * @param int $timeout
1105 96
     * @param string $username
1106
     * @param string $password
1107
     * @param int $authType only value supported is 1
1108
     * @param string $cert
1109
     * @param string $certPass
1110 96
     * @param string $caCert
1111
     * @param string $caCertDir
1112 96
     * @param string $proxyHost
1113
     * @param int $proxyPort
1114
     * @param string $proxyUsername
1115
     * @param string $proxyPassword
1116 323
     * @param int $proxyAuthType only value supported is 1
1117 64
     * @param string $method 'http' (synonym for 'http10'), 'http10' or 'https'
1118
     * @param string $key
1119
     * @param string $keyPass @todo not implemented yet.
1120 64
     * @param int $sslVersion @todo not implemented yet. See http://php.net/manual/en/migration56.openssl.php
1121 64
     * @return Response
1122
     *
1123
     * @todo refactor: we get many options for the call passed in, but some we use from $this. We should clean that up
1124
     */
1125
    protected function sendPayloadSocket($req, $server, $port, $timeout = 0, $username = '', $password = '',
1126
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
1127
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method='http', $key = '', $keyPass = '',
0 ignored issues
show
Unused Code introduced by
The parameter $keyPass is not used and could be removed. ( Ignorable by Annotation )

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

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

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

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

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

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

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

Loading history...
1129
    {
1130
        /// @todo log a warning if passed an unsupported method
1131
1132
        // Only create the payload if it was not created previously
1133 323
        /// @todo what if the request's payload was created with a different encoding?
1134 271
        if (empty($req->payload)) {
1135 271
            $req->serialize($this->request_charset_encoding);
1136 271
        }
1137
        $payload = $req->payload;
1138 271
1139
        // Deflate request body and set appropriate request headers
1140
        $encodingHdr = '';
1141 323
        if (function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate')) {
1142
            if ($this->request_compression == 'gzip') {
1143
                $a = @gzencode($payload);
1144
                if ($a) {
1145 323
                    $payload = $a;
1146
                    $encodingHdr = "Content-Encoding: gzip\r\n";
1147
                }
1148
            } else {
1149 323
                $a = @gzcompress($payload);
1150
                if ($a) {
1151
                    $payload = $a;
1152
                    $encodingHdr = "Content-Encoding: deflate\r\n";
1153
                }
1154
            }
1155
        }
1156
1157
        // thanks to Grant Rauscher <[email protected]> for this
1158
        $credentials = '';
1159
        if ($username != '') {
1160
            $credentials = 'Authorization: Basic ' . base64_encode($username . ':' . $password) . "\r\n";
1161
            if ($authType != 1) {
1162
                /// @todo make this a proper error, i.e. return a failure
1163
                $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported with HTTP 1.0');
1164
            }
1165
        }
1166
1167
        $acceptedEncoding = '';
1168
        if (is_array($this->accepted_compression) && count($this->accepted_compression)) {
1169
            $acceptedEncoding = 'Accept-Encoding: ' . implode(', ', $this->accepted_compression) . "\r\n";
1170
        }
1171
1172
        if ($port == 0) {
1173
            $port = ($method === 'https') ? 443 : 80;
1174 66
        }
1175
1176 66
        $proxyCredentials = '';
1177
        if ($proxyHost) {
1178
            if ($proxyPort == 0) {
1179 66
                $proxyPort = 8080;
1180 45
            }
1181 45
            $connectServer = $proxyHost;
1182
            $connectPort = $proxyPort;
1183 45
            $transport = 'tcp';
1184
            /// @todo check: should we not use https in some cases?
1185
            $uri = 'http://' . $server . ':' . $port . $this->path;
1186
            if ($proxyUsername != '') {
1187
                if ($proxyAuthType != 1) {
1188
                    /// @todo make this a proper error, i.e. return a failure
1189
                    $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported with HTTP 1.0');
1190
                }
1191
                $proxyCredentials = 'Proxy-Authorization: Basic ' . base64_encode($proxyUsername . ':' . $proxyPassword) . "\r\n";
1192
            }
1193
        } else {
1194
            $connectServer = $server;
1195
            $connectPort = $port;
1196
            $transport = ($method === 'https') ? 'tls' : 'tcp';
1197
            $uri = $this->path;
1198
        }
1199
1200
        // Cookie generation, as per rfc2965
1201 23
        $cookieHeader = '';
1202
        if (count($this->cookies)) {
1203
            $version = '';
1204 23
            foreach ($this->cookies as $name => $cookie) {
1205 23
                /// @todo should we sanitize the cookie name/value on behalf of the user?
1206
                $cookieHeader .= ' ' . $name . '=' . $cookie['value'] . ";";
1207
            }
1208
            $cookieHeader = 'Cookie:' . $version . substr($cookieHeader, 0, -1) . "\r\n";
1209 23
        }
1210 23
1211
        // omit port if default
1212
        if (($port == 80 && in_array($method, array('http', 'http10'))) || ($port == 443 && $method == 'https')) {
1213
            $port =  '';
1214
        } else {
1215
            $port = ':' . $port;
1216
        }
1217
1218
        $op = 'POST ' . $uri . " HTTP/1.0\r\n" .
1219
            'User-Agent: ' . $this->user_agent . "\r\n" .
1220
            'Host: ' . $server . $port . "\r\n" .
1221 23
            $credentials .
1222
            $proxyCredentials .
1223
            $acceptedEncoding .
1224
            $encodingHdr .
1225
            'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings) . "\r\n" .
1226
            $cookieHeader .
1227
            'Content-Type: ' . $req->content_type . "\r\nContent-Length: " .
1228
            strlen($payload) . "\r\n\r\n" .
1229
            $payload;
1230
1231
        if ($this->debug > 1) {
1232
            $this->getLogger()->debug("---SENDING---\n$op\n---END---");
1233
        }
1234
1235 45
        $contextOptions = array();
1236
        if ($method == 'https') {
1237
            if ($cert != '') {
1238 45
                $contextOptions['ssl']['local_cert'] = $cert;
1239 45
                if ($certPass != '') {
1240 45
                    $contextOptions['ssl']['passphrase'] = $certPass;
1241 45
                }
1242 45
            }
1243 45
            if ($caCert != '') {
1244 45
                $contextOptions['ssl']['cafile'] = $caCert;
1245
            }
1246 45
            if ($caCertDir != '') {
1247 45
                $contextOptions['ssl']['capath'] = $caCertDir;
1248
            }
1249 45
            if ($key != '') {
1250 45
                $contextOptions['ssl']['local_pk'] = $key;
1251
            }
1252
            $contextOptions['ssl']['verify_peer'] = $this->verifypeer;
1253 45
            $contextOptions['ssl']['verify_peer_name'] = $this->verifypeer;
1254
        }
1255 45
1256
        $context = stream_context_create($contextOptions);
1257
1258
        if ($timeout <= 0) {
1259
            $connectTimeout = ini_get('default_socket_timeout');
1260
        } else {
1261 45
            $connectTimeout = $timeout;
1262
        }
1263 45
1264
        $this->errno = 0;
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errno has been deprecated: will be removed in the future ( Ignorable by Annotation )

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

1264
        /** @scrutinizer ignore-deprecated */ $this->errno = 0;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1265 45
        $this->errstr = '';
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

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

1265
        /** @scrutinizer ignore-deprecated */ $this->errstr = '';

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1266
1267 22
        $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr, $connectTimeout,
0 ignored issues
show
Bug introduced by
It seems like $connectTimeout can also be of type string; however, parameter $timeout of stream_socket_client() does only seem to accept double|null, maybe add an additional type check? ( Ignorable by Annotation )

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

1267
        $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr, /** @scrutinizer ignore-type */ $connectTimeout,
Loading history...
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errno has been deprecated: will be removed in the future ( Ignorable by Annotation )

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

1267
        $fp = @stream_socket_client("$transport://$connectServer:$connectPort", /** @scrutinizer ignore-deprecated */ $this->errno, $this->errstr, $connectTimeout,

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

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

1267
        $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, /** @scrutinizer ignore-deprecated */ $this->errstr, $connectTimeout,

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1268 22
            STREAM_CLIENT_CONNECT, $context);
1269
        if ($fp) {
0 ignored issues
show
introduced by
$fp is of type false|resource, thus it always evaluated to false.
Loading history...
1270
            if ($timeout > 0) {
1271 22
                stream_set_timeout($fp, $timeout, 0);
1272 22
            }
1273
        } else {
1274
            if ($this->errstr == '') {
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

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

1274
            if (/** @scrutinizer ignore-deprecated */ $this->errstr == '') {

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1275
                $err = error_get_last();
1276 22
                $this->errstr = $err['message'];
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

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

1276
                /** @scrutinizer ignore-deprecated */ $this->errstr = $err['message'];

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1277 22
            }
1278 22
1279 22
            $this->errstr = 'Connect error: ' . $this->errstr;
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

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

1279
            /** @scrutinizer ignore-deprecated */ $this->errstr = 'Connect error: ' . $this->errstr;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1280
            $r = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr . ' (' . $this->errno . ')');
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

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

1280
            $r = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], /** @scrutinizer ignore-deprecated */ $this->errstr . ' (' . $this->errno . ')');

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errno has been deprecated: will be removed in the future ( Ignorable by Annotation )

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

1280
            $r = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr . ' (' . /** @scrutinizer ignore-deprecated */ $this->errno . ')');

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1281
1282 22
            return $r;
1283 22
        }
1284 22
1285
        if (!fputs($fp, $op, strlen($op))) {
1286
            fclose($fp);
1287
            $this->errstr = 'Write error';
1288 22
            $r = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr);
1289 22
1290 22
            return $r;
1291
        }
1292 22
1293 22
        // Close socket before parsing.
1294
        // It should yield slightly better execution times, and make easier recursive calls (e.g. to follow http redirects)
1295
        $ipd = '';
1296 22
        do {
1297 22
            // shall we check for $data === FALSE?
1298
            // as per the manual, it signals an error
1299
            $ipd .= fread($fp, 32768);
1300 22
        } while (!feof($fp));
1301 22
        fclose($fp);
1302
1303
        $r = $req->parseResponse($ipd, false, $this->return_type);
1304
1305
        return $r;
1306
    }
1307 22
1308
    /**
1309
     * Contributed by Justin Miller <[email protected]>
1310
     * Requires curl to be built into PHP
1311 24
     * NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers!
1312 24
     *
1313
     * @param Request $req
1314
     * @param string $server
1315 24
     * @param int $port
1316 24
     * @param int $timeout
1317
     * @param string $username
1318
     * @param string $password
1319
     * @param int $authType
1320 24
     * @param string $cert
1321 24
     * @param string $certPass
1322 24
     * @param string $caCert
1323 24
     * @param string $caCertDir
1324 24
     * @param string $proxyHost
1325
     * @param int $proxyPort
1326
     * @param string $proxyUsername
1327
     * @param string $proxyPassword
1328 24
     * @param int $proxyAuthType
1329 24
     * @param string $method 'http' (let curl decide), 'http10', 'http11', 'https', 'h2c' or 'h2'
1330 22
     * @param bool $keepAlive
1331 22
     * @param string $key
1332
     * @param string $keyPass
1333 22
     * @param int $sslVersion
1334
     * @return Response
1335
     *
1336 22
     * @todo refactor: we get many options for the call passed in, but some we use from $this. We should clean that up
1337
     */
1338 22
    protected function sendPayloadCURL($req, $server, $port, $timeout = 0, $username = '', $password = '',
1339
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
1340
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'https', $keepAlive = false, $key = '',
1341 22
        $keyPass = '', $sslVersion = 0)
1342 22
    {
1343
        if (!function_exists('curl_init')) {
1344
            $this->errstr = 'CURL unavailable on this install';
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

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

1344
            /** @scrutinizer ignore-deprecated */ $this->errstr = 'CURL unavailable on this install';

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1345
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_curl'], PhpXmlRpc::$xmlrpcstr['no_curl']);
1346
        }
1347
        if ($method == 'https' || $method == 'h2') {
1348 24
            // q: what about installs where we get back a string, but curl is linked to other ssl libs than openssl?
1349
            if (($info = curl_version()) &&
1350
                ((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...
1351
            ) {
1352
                $this->errstr = 'SSL unavailable on this install';
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

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

1352
                /** @scrutinizer ignore-deprecated */ $this->errstr = 'SSL unavailable on this install';

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1353
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_ssl'], PhpXmlRpc::$xmlrpcstr['no_ssl']);
1354
            }
1355
        }
1356
        if (($method == 'h2' && !defined('CURL_HTTP_VERSION_2_0')) ||
1357
            ($method == 'h2c' && !defined('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE'))) {
1358
            $this->errstr = 'HTTP/2 unavailable on this install';
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

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

1358
            /** @scrutinizer ignore-deprecated */ $this->errstr = 'HTTP/2 unavailable on this install';

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1359
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_http2'], PhpXmlRpc::$xmlrpcstr['no_http2']);
1360
        }
1361
1362
        $curl = $this->prepareCurlHandle($req, $server, $port, $timeout, $username, $password,
1363
            $authType, $cert, $certPass, $caCert, $caCertDir, $proxyHost, $proxyPort,
1364
            $proxyUsername, $proxyPassword, $proxyAuthType, $method, $keepAlive, $key,
1365
            $keyPass, $sslVersion);
1366
1367
        if (!$curl) {
1368
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] . ': error during curl initialization. Check php error log for details');
1369
        }
1370
1371
        $result = curl_exec($curl);
1372
1373
        if ($this->debug > 1) {
1374
            $message = "---CURL INFO---\n";
1375
            foreach (curl_getinfo($curl) as $name => $val) {
1376
                if (is_array($val)) {
1377
                    $val = implode("\n", $val);
1378
                }
1379
                $message .= $name . ': ' . $val . "\n";
1380
            }
1381
            $message .= '---END---';
1382
            $this->getLogger()->debug($message);
1383
        }
1384
1385
        if (!$result) {
1386
            /// @todo we should use a better check here - what if we get back '' or '0'?
1387
1388
            $this->errstr = 'no response';
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

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

1388
            /** @scrutinizer ignore-deprecated */ $this->errstr = 'no response';

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1389
            $resp = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] . ': ' . curl_error($curl));
1390
            curl_close($curl);
1391
            if ($keepAlive) {
1392
                $this->xmlrpc_curl_handle = null;
1393
            }
1394
        } else {
1395
            if (!$keepAlive) {
1396
                curl_close($curl);
1397
            }
1398
            $resp = $req->parseResponse($result, true, $this->return_type);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type true; however, parameter $data of PhpXmlRpc\Request::parseResponse() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

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

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

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

Loading history...
1794
                return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1795
                    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 1705. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
1796
                    $result->httpResponse());
1797
            }
1798
            $numRets = $rets->count();
1799
            if ($numRets != count($reqs)) {
1800
                // wrong number of return values.
1801
                return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1802
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': incorrect number of responses', 'xmlrpcvals',
1803
                    $result->httpResponse());
1804
            }
1805
1806
            foreach ($rets as $i => $val) {
0 ignored issues
show
Bug introduced by
The expression $rets of type integer is not traversable.
Loading history...
1807
                switch ($val->kindOf()) {
1808
                    case 'array':
1809
                        if ($val->count() != 1) {
1810
                            return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1811
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1812
                                'phpvals', $result->httpResponse());
1813
                        }
1814
                        // Normal return value
1815
                        $response[] = new Response($val[0], 0, '', 'xmlrpcvals', $result->httpResponse());
1816
                        break;
1817
                    case 'struct':
1818
                        if ($val->count() != 2) {
1819
                            return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1820
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1821
                                'phpvals', $result->httpResponse());
1822
                        }
1823
                        /** @var Value $code */
1824
                        $code = $val['faultCode'];
1825
                        if ($code->kindOf() != 'scalar' || $code->scalarTyp() != 'int') {
1826
                            return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1827
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1828
                                'xmlrpcvals', $result->httpResponse());
1829
                        }
1830
                        /** @var Value $str */
1831
                        $str = $val['faultString'];
1832
                        if ($str->kindOf() != 'scalar' || $str->scalarTyp() != 'string') {
1833
                            return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1834
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1835
                                'xmlrpcvals', $result->httpResponse());
1836
                        }
1837
                        $response[] = new Response(0, $code->scalarVal(), $str->scalarVal(), 'xmlrpcvals', $result->httpResponse());
1838
                        break;
1839
                    default:
1840
                        return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1841
                            PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array or struct",
1842
                            'xmlrpcvals', $result->httpResponse());
1843
                }
1844
            }
1845
        }
1846
1847
        return $response;
1848
    }
1849
}
1850