Passed
Push — master ( 6f0f8c...d68788 )
by Gaetano
06:26
created

Client::sendPayloadHTTPS()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1.125

Importance

Changes 0
Metric Value
cc 1
eloc 3
c 0
b 0
f 0
nc 1
nop 20
dl 0
loc 10
ccs 3
cts 6
cp 0.5
crap 1.125
rs 10

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
3
namespace PhpXmlRpc;
4
5
//use PhpXmlRpc\Helper\Charset;
6
use PhpXmlRpc\Exception\ValueErrorException;
7
use PhpXmlRpc\Helper\XMLParser;
8
use PhpXmlRpc\Traits\LoggerAware;
9
10
/**
11
 * Used to represent a client of an XML-RPC server.
12
 */
13
class Client
14
{
15
    use LoggerAware;
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
     * @deprecated use setOption
633
     */
634
    public function setDebug($level)
635
    {
636
        $this->debug = $level;
637
        return $this;
638
    }
639
640
    /**
641
     * Sets the username and password for authorizing the client to the server.
642
     *
643
     * With the default (HTTP) transport, this information is used for HTTP Basic authorization.
644
     * Note that username and password can also be set using the class constructor.
645
     * With HTTP 1.1 and HTTPS transport, NTLM and Digest authentication protocols are also supported. To enable them use
646
     * the constants CURLAUTH_DIGEST and CURLAUTH_NTLM as values for the auth type parameter.
647
     *
648
     * @param string $user username
649
     * @param string $password password
650
     * @param integer $authType auth type. See curl_setopt man page for supported auth types. Defaults to CURLAUTH_BASIC
651
     *                          (basic auth). Note that auth types NTLM and Digest will only work if the Curl php
652
     *                          extension is enabled.
653
     * @return $this
654
     */
655
    public function setCredentials($user, $password, $authType = 1)
656
    {
657
        $this->username = $user;
658
        $this->password = $password;
659
        $this->authtype = $authType;
660
        return $this;
661
    }
662
663
    /**
664 375
     * Set the optional certificate and passphrase used in SSL-enabled communication with a remote server.
665
     *
666
     * Note: to retrieve information about the client certificate on the server side, you will need to look into the
667
     * environment variables which are set up by the webserver. Different webservers will typically set up different
668
     * variables.
669
     *
670
     * @param string $cert the name of a file containing a PEM formatted certificate
671 375
     * @param string $certPass the password required to use it
672 373
     * @return $this
673
     */
674
    public function setCertificate($cert, $certPass = '')
675
    {
676 375
        $this->cert = $cert;
677 346
        $this->certpass = $certPass;
678
        return $this;
679
    }
680 375
681
    /**
682 375
     * Add a CA certificate to verify server with in SSL-enabled communication when SetSSLVerifypeer has been set to TRUE.
683 375
     *
684 64
     * See the php manual page about CURLOPT_CAINFO for more details.
685 32
     *
686 32
     * @param string $caCert certificate file name (or dir holding certificates)
687 32
     * @param bool $isDir set to true to indicate cacert is a dir. defaults to false
688 32
     * @return $this
689
     */
690
    public function setCaCertificate($caCert, $isDir = false)
691 32
    {
692 32
        if ($isDir) {
693 32
            $this->cacertdir = $caCert;
694 32
        } else {
695
            $this->cacert = $caCert;
696
        }
697
        return $this;
698
    }
699
700 375
    /**
701 375
     * Set attributes for SSL communication: private SSL key.
702 32
     *
703 32
     * NB: does not work in older php/curl installs.
704
     * Thanks to Daniel Convissor.
705
     *
706
     * @param string $key The name of a file containing a private SSL key
707
     * @param string $keyPass The secret password needed to use the private SSL key
708 375
     * @return $this
709 375
     */
710 73
    public function setKey($key, $keyPass)
711
    {
712
        $this->key = $key;
713 375
        $this->keypass = $keyPass;
714 375
        return $this;
715 32
    }
716
717
    /**
718 32
     * Set attributes for SSL communication: verify the remote host's SSL certificate, and cause the connection to fail
719 32
     * if the cert verification fails.
720 32
     *
721 32
     * By default, verification is enabled.
722 32
     * To specify custom SSL certificates to validate the server with, use the setCaCertificate method.
723
     *
724
     * @param bool $i enable/disable verification of peer certificate
725
     * @return $this
726 32
     * @deprecated use setOption
727
     */
728
    public function setSSLVerifyPeer($i)
729 343
    {
730 343
        $this->verifypeer = $i;
731 343
        return $this;
732 343
    }
733
734
    /**
735
     * Set attributes for SSL communication: verify the remote host's SSL certificate's common name (CN).
736 375
     *
737 375
     * Note that support for value 1 has been removed in cURL 7.28.1
738 309
     *
739 309
     * @param int $i Set to 1 to only the existence of a CN, not that it matches
740 309
     * @return $this
741
     * @deprecated use setOption
742
     */
743
    public function setSSLVerifyHost($i)
744
    {
745
        $this->verifyhost = $i;
746
        return $this;
747
    }
748
749
    /**
750
     * Set attributes for SSL communication: SSL version to use. Best left at 0 (default value): let cURL decide
751
     *
752
     * @param int $i
753 309
     * @return $this
754
     * @deprecated use setOption
755
     */
756 309
    public function setSSLVersion($i)
757
    {
758
        $this->sslversion = $i;
759
        return $this;
760 375
    }
761 375
762
    /**
763
     * Set proxy info.
764
     *
765
     * NB: CURL versions before 7.11.10 cannot use a proxy to communicate with https servers.
766 375
     *
767 375
     * @param string $proxyHost
768 375
     * @param string $proxyPort Defaults to 8080 for HTTP and 443 for HTTPS
769 375
     * @param string $proxyUsername Leave blank if proxy has public access
770 375
     * @param string $proxyPassword Leave blank if proxy has public access
771 375
     * @param int $proxyAuthType defaults to CURLAUTH_BASIC (Basic authentication protocol); set to constant CURLAUTH_NTLM
772 375
     *                           to use NTLM auth with proxy (has effect only when the client uses the HTTP 1.1 protocol)
773 375
     * @return $this
774 375
     */
775 375
    public function setProxy($proxyHost, $proxyPort, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1)
776 375
    {
777 375
        $this->proxy = $proxyHost;
778
        $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...
779 375
        $this->proxy_user = $proxyUsername;
780 2
        $this->proxy_pass = $proxyPassword;
781
        $this->proxy_authtype = $proxyAuthType;
782
        return $this;
783 375
    }
784 375
785 32
    /**
786
     * Enables/disables reception of compressed xml-rpc responses.
787
     *
788
     * This requires the "zlib" extension to be enabled in your php install. If it is, by default xmlrpc_client
789
     * instances will enable reception of compressed content.
790
     * Note that enabling reception of compressed responses merely adds some standard http headers to xml-rpc requests.
791 32
     * It is up to the xml-rpc server to return compressed responses when receiving such requests.
792
     *
793
     * @param string $compMethod either 'gzip', 'deflate', 'any' or ''
794 32
     * @return $this
795
     */
796
    public function setAcceptedCompression($compMethod)
797 32
    {
798
        if ($compMethod == 'any') {
799
            $this->accepted_compression = array('gzip', 'deflate');
800 32
        } 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...
801 32
            $this->accepted_compression = array();
802
        } else {
803
            $this->accepted_compression = array($compMethod);
804 375
        }
805
        return $this;
806 375
    }
807 64
808
    /**
809 311
     * Enables/disables http compression of xml-rpc request.
810
     *
811
     * This requires the "zlib" extension to be enabled in your php install.
812 375
     * Take care when sending compressed requests: servers might not support them (and automatic fallback to
813 375
     * uncompressed requests is not yet implemented).
814
     *
815 375
     * @param string $compMethod either 'gzip', 'deflate' or ''
816 375
     * @return $this
817 375
     * @deprecated use setOption
818 374
     */
819 367
    public function setRequestCompression($compMethod)
820
    {
821
        $this->request_compression = $compMethod;
822 1
        return $this;
823
    }
824
825
    /**
826
     * Adds a cookie to list of cookies that will be sent to server with every further request (useful e.g. for keeping
827 1
     * session info outside of the xml-rpc payload).
828 1
     *
829
     * NB: By default cookies are sent using the 'original/netscape' format, which is also the same as the RFC 2965;
830 1
     * setting any param but name and value will turn the cookie into a 'version 1' cookie (i.e. RFC 2109 cookie) that
831
     * might not be fully supported by the server. Note that RFC 2109 has currently 'historic' status...
832
     *
833 374
     * @param string $name nb: will not be escaped in the request's http headers. Take care not to use CTL chars or
834
     *                     separators!
835
     * @param string $value
836
     * @param string $path leave this empty unless the xml-rpc server only accepts RFC 2109 cookies
837
     * @param string $domain leave this empty unless the xml-rpc server only accepts RFC 2109 cookies
838
     * @param int $port leave this empty unless the xml-rpc server only accepts RFC 2109 cookies
839
     * @return $this
840
     *
841
     * @todo check correctness of urlencoding cookie value (copied from php way of doing it, but php is generally sending
842
     *       response not requests. We do the opposite...)
843 374
     * @todo strip invalid chars from cookie name? As per RFC6265, we should follow RFC2616, Section 2.2
844
     */
845
    public function setCookie($name, $value = '', $path = '', $domain = '', $port = null)
846
    {
847 374
        $this->cookies[$name]['value'] = rawurlencode($value);
848 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...
849 374
            $this->cookies[$name]['path'] = $path;
850
            $this->cookies[$name]['domain'] = $domain;
851 374
            $this->cookies[$name]['port'] = $port;
852
            $this->cookies[$name]['version'] = 1;
853 374
        } else {
854
            $this->cookies[$name]['version'] = 0;
855
        }
856
        return $this;
857
    }
858
859
    /**
860
     * Directly set cURL options, for extra flexibility (when in cURL mode).
861
     *
862
     * It allows e.g. to bind client to a specific IP interface / address.
863
     *
864
     * @param array $options
865
     * @return $this
866
     * @deprecated use setOption
867
     */
868
    public function setCurlOptions($options)
869
    {
870
        $this->extracurlopts = $options;
871
        return $this;
872
    }
873
874
    /**
875
     * @param int $useCurlMode self::USE_CURL_ALWAYS, self::USE_CURL_AUTO or self::USE_CURL_NEVER
876
     * @return $this
877
     * @deprecated use setOption
878
     */
879
    public function setUseCurl($useCurlMode)
880
    {
881
        $this->use_curl = $useCurlMode;
882
        return $this;
883
    }
884
885
886 322
    /**
887
     * Set user-agent string that will be used by this client instance in http headers sent to the server.
888
     *
889
     * The default user agent string includes the name of this library and the version number.
890
     *
891 322
     * @param string $agentString
892
     * @return $this
893
     * @deprecated use setOption
894
     */
895 322
    public function setUserAgent($agentString)
896
    {
897 96
        $this->user_agent = $agentString;
898 96
        return $this;
899
    }
900
901
    /**
902
     * @return string
903
     */
904 322
    public function getUrl()
905 322
    {
906
        $url = $this->method . '://' . $this->server;
907
        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...
908
            ($this->port = 443 && in_array($this->method, array('https', 'h2')))) {
909
            return $url . $this->path;
910 322
        } else {
911
            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

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

1128
        $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...
1129
        $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

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

1265
        /** @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...
1266
        $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

1266
        /** @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...
1267 22
1268 22
        $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr, $connectTimeout,
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

1268
        $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

1268
        $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...
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

1268
        $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr, /** @scrutinizer ignore-type */ $connectTimeout,
Loading history...
1269
            STREAM_CLIENT_CONNECT, $context);
1270
        if ($fp) {
0 ignored issues
show
introduced by
$fp is of type false|resource, thus it always evaluated to false.
Loading history...
1271 22
            if ($timeout > 0) {
1272 22
                stream_set_timeout($fp, $timeout, 0);
1273
            }
1274
        } else {
1275
            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

1275
            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...
1276 22
                $err = error_get_last();
1277 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

1277
                /** @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...
1278 22
            }
1279 22
1280
            $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

1280
            $this->errstr = 'Connect error: ' . /** @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...
1281
            $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

1281
            $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

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

1345
            /** @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...
1346
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_curl'], PhpXmlRpc::$xmlrpcstr['no_curl']);
1347
        }
1348 24
        if ($method == 'https' || $method == 'h2') {
1349
            // q: what about installs where we get back a string, but curl is linked to other ssl libs than openssl?
1350
            if (($info = curl_version()) &&
1351
                ((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...
1352
            ) {
1353
                $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

1353
                /** @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...
1354
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_ssl'], PhpXmlRpc::$xmlrpcstr['no_ssl']);
1355
            }
1356
        }
1357
        if (($method == 'h2' && !defined('CURL_HTTP_VERSION_2_0')) ||
1358
            ($method == 'h2c' && !defined('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE'))) {
1359
            $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

1359
            /** @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...
1360
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_http2'], PhpXmlRpc::$xmlrpcstr['no_http2']);
1361
        }
1362
1363
        $curl = $this->prepareCurlHandle($req, $server, $port, $timeout, $username, $password,
1364
            $authType, $cert, $certPass, $caCert, $caCertDir, $proxyHost, $proxyPort,
1365
            $proxyUsername, $proxyPassword, $proxyAuthType, $method, $keepAlive, $key,
1366
            $keyPass, $sslVersion);
1367
1368
        if (!$curl) {
1369
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] . ': error during curl initialization. Check php error log for details');
1370
        }
1371
1372
        $result = curl_exec($curl);
1373
1374
        if ($this->debug > 1) {
1375
            $message = "---CURL INFO---\n";
1376
            foreach (curl_getinfo($curl) as $name => $val) {
1377
                if (is_array($val)) {
1378
                    $val = implode("\n", $val);
1379
                }
1380
                $message .= $name . ': ' . $val . "\n";
1381
            }
1382
            $message .= '---END---';
1383
            $this->getLogger()->debug($message);
1384
        }
1385
1386
        if (!$result) {
1387
            /// @todo we should use a better check here - what if we get back '' or '0'?
1388
1389
            $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

1389
            /** @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...
1390
            $resp = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] . ': ' . curl_error($curl));
1391
            curl_close($curl);
1392
            if ($keepAlive) {
1393
                $this->xmlrpc_curl_handle = null;
1394
            }
1395
        } else {
1396
            if (!$keepAlive) {
1397
                curl_close($curl);
1398
            }
1399
            $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

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

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