Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
 
3
namespace PhpXmlRpc;
4
 
5
use PhpXmlRpc\Exception\ValueErrorException;
6
use PhpXmlRpc\Helper\XMLParser;
7
use PhpXmlRpc\Traits\CharsetEncoderAware;
8
use PhpXmlRpc\Traits\DeprecationLogger;
9
 
10
/**
11
 * Used to represent a client of an XML-RPC server.
12
 *
13
 * @property int $errno deprecated - public access left in purely for BC.
14
 * @property string $errstr deprecated - public access left in purely for BC.
15
 * @property string $method deprecated - public access left in purely for BC. Access via getUrl()/__construct()
16
 * @property string $server deprecated - public access left in purely for BC. Access via getUrl()/__construct()
17
 * @property int $port deprecated - public access left in purely for BC. Access via getUrl()/__construct()
18
 * @property string $path deprecated - public access left in purely for BC. Access via getUrl()/__construct()
19
 */
20
class Client
21
{
22
    use DeprecationLogger;
23
    //use CharsetEncoderAware;
24
 
25
    const USE_CURL_NEVER = 0;
26
    const USE_CURL_ALWAYS = 1;
27
    const USE_CURL_AUTO = 2;
28
 
29
    const OPT_ACCEPTED_CHARSET_ENCODINGS = 'accepted_charset_encodings';
30
    const OPT_ACCEPTED_COMPRESSION = 'accepted_compression';
31
    const OPT_AUTH_TYPE = 'authtype';
32
    const OPT_CA_CERT = 'cacert';
33
    const OPT_CA_CERT_DIR = 'cacertdir';
34
    const OPT_CERT = 'cert';
35
    const OPT_CERT_PASS = 'certpass';
36
    const OPT_COOKIES = 'cookies';
37
    const OPT_DEBUG = 'debug';
38
    const OPT_EXTRA_CURL_OPTS = 'extracurlopts';
39
    const OPT_EXTRA_SOCKET_OPTS = 'extrasockopts';
40
    const OPT_KEEPALIVE = 'keepalive';
41
    const OPT_KEY = 'key';
42
    const OPT_KEY_PASS = 'keypass';
43
    const OPT_NO_MULTICALL = 'no_multicall';
44
    const OPT_PASSWORD = 'password';
45
    const OPT_PROXY = 'proxy';
46
    const OPT_PROXY_AUTH_TYPE = 'proxy_authtype';
47
    const OPT_PROXY_PASS = 'proxy_pass';
48
    const OPT_PROXY_PORT = 'proxyport';
49
    const OPT_PROXY_USER = 'proxy_user';
50
    const OPT_REQUEST_CHARSET_ENCODING = 'request_charset_encoding';
51
    const OPT_REQUEST_COMPRESSION = 'request_compression';
52
    const OPT_RETURN_TYPE = 'return_type';
53
    const OPT_SSL_VERSION = 'sslversion';
54
    const OPT_TIMEOUT = 'timeout';
55
    const OPT_USERNAME = 'username';
56
    const OPT_USER_AGENT = 'user_agent';
57
    const OPT_USE_CURL = 'use_curl';
58
    const OPT_VERIFY_HOST = 'verifyhost';
59
    const OPT_VERIFY_PEER = 'verifypeer';
60
 
61
    /** @var string */
62
    protected static $requestClass = '\\PhpXmlRpc\\Request';
63
    /** @var string */
64
    protected static $responseClass = '\\PhpXmlRpc\\Response';
65
 
66
    /**
67
     * @var int
68
     * @deprecated will be removed in the future
69
     */
70
    protected $errno;
71
    /**
72
     * @var string
73
     * @deprecated will be removed in the future
74
     */
75
    protected $errstr;
76
 
77
    /// @todo: do all the ones below need to be public?
78
 
79
    /**
80
     * @var string
81
     */
82
    protected $method = 'http';
83
    /**
84
     * @var string
85
     */
86
    protected $server;
87
    /**
88
     * @var int
89
     */
90
    protected $port = 0;
91
    /**
92
     * @var string
93
     */
94
    protected $path;
95
 
96
    /**
97
     * @var int
98
     */
99
    protected $debug = 0;
100
    /**
101
     * @var string
102
     */
103
    protected $username = '';
104
    /**
105
     * @var string
106
     */
107
    protected $password = '';
108
    /**
109
     * @var int
110
     */
111
    protected $authtype = 1;
112
    /**
113
     * @var string
114
     */
115
    protected $cert = '';
116
    /**
117
     * @var string
118
     */
119
    protected $certpass = '';
120
    /**
121
     * @var string
122
     */
123
    protected $cacert = '';
124
    /**
125
     * @var string
126
     */
127
    protected $cacertdir = '';
128
    /**
129
     * @var string
130
     */
131
    protected $key = '';
132
    /**
133
     * @var string
134
     */
135
    protected $keypass = '';
136
    /**
137
     * @var bool
138
     */
139
    protected $verifypeer = true;
140
    /**
141
     * @var int
142
     */
143
    protected $verifyhost = 2;
144
    /**
145
     * @var int
146
     */
147
    protected $sslversion = 0; // corresponds to CURL_SSLVERSION_DEFAULT. Other  CURL_SSLVERSION_ values are supported
148
    /**
149
     * @var string
150
     */
151
    protected $proxy = '';
152
    /**
153
     * @var int
154
     */
155
    protected $proxyport = 0;
156
    /**
157
     * @var string
158
     */
159
    protected $proxy_user = '';
160
    /**
161
     * @var string
162
     */
163
    protected $proxy_pass = '';
164
    /**
165
     * @var int
166
     */
167
    protected $proxy_authtype = 1;
168
    /**
169
     * @var array
170
     */
171
    protected $cookies = array();
172
    /**
173
     * @var array
174
     */
175
    protected $extrasockopts = array();
176
    /**
177
     * @var array
178
     */
179
    protected $extracurlopts = array();
180
    /**
181
     * @var int
182
     */
183
    protected $timeout = 0;
184
    /**
185
     * @var int
186
     */
187
    protected $use_curl = self::USE_CURL_AUTO;
188
    /**
189
     * @var bool
190
     *
191
     * This determines whether the multicall() method will try to take advantage of the system.multicall xml-rpc method
192
     * to dispatch to the server an array of requests in a single http roundtrip or simply execute many consecutive http
193
     * calls. Defaults to FALSE, but it will be enabled automatically on the first failure of execution of
194
     * system.multicall.
195
     */
196
    protected $no_multicall = false;
197
    /**
198
     * @var array
199
     *
200
     * List of http compression methods accepted by the client for responses.
201
     * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib.
202
     *
203
     * NNB: you can set it to any non-empty array for HTTP11 and HTTPS, since in those cases it will be up to CURL to
204
     * decide the compression methods it supports. You might check for the presence of 'zlib' in the output of
205
     * curl_version() to determine whether compression is supported or not
206
     */
207
    protected $accepted_compression = array();
208
    /**
209
     * @var string|null
210
     *
211
     * Name of compression scheme to be used for sending requests.
212
     * Either null, 'gzip' or 'deflate'.
213
     */
214
    protected $request_compression = '';
215
    /**
216
     * @var bool
217
     *
218
     * Whether to use persistent connections for http 1.1 and https. Value set at constructor time.
219
     */
220
    protected $keepalive = false;
221
    /**
222
     * @var string[]
223
     *
224
     * Charset encodings that can be decoded without problems by the client. Value set at constructor time
225
     */
226
    protected $accepted_charset_encodings = array();
227
    /**
228
     * @var string
229
     *
230
     * The charset encoding that will be used for serializing request sent by the client.
231
     * It defaults to NULL, which means using US-ASCII and encoding all characters outside the ASCII printable range
232
     * using their xml character entity representation (this has the benefit that line end characters will not be mangled
233
     * in the transfer, a CR-LF will be preserved as well as a singe LF).
234
     * Valid values are 'US-ASCII', 'UTF-8' and 'ISO-8859-1'.
235
     * For the fastest mode of operation, set your both your app internal encoding and this to UTF-8.
236
     */
237
    protected $request_charset_encoding = '';
238
    /**
239
     * @var string
240
     *
241
     * Decides the content of Response objects returned by calls to send() and multicall().
242
     * Valid values are 'xmlrpcvals', 'phpvals' or 'xml'.
243
     *
244
     * Determines whether the value returned inside a Response object as results of calls to the send() and multicall()
245
     * methods will be a Value object, a plain php value or a raw xml string.
246
     * Allowed values are 'xmlrpcvals' (the default), 'phpvals' and 'xml'.
247
     * To allow the user to differentiate between a correct and a faulty response, fault responses will be returned as
248
     * Response objects in any case.
249
     * Note that the 'phpvals' setting will yield faster execution times, but some of the information from the original
250
     * response will be lost. It will be e.g. impossible to tell whether a particular php string value was sent by the
251
     * server as an xml-rpc string or base64 value.
252
     */
253
    protected $return_type = XMLParser::RETURN_XMLRPCVALS;
254
    /**
255
     * @var string
256
     *
257
     * Sent to servers in http headers. Value set at constructor time.
258
     */
259
    protected $user_agent;
260
 
261
    /**
262
     * CURL handle: used for keep-alive
263
     * @internal
264
     */
265
    public $xmlrpc_curl_handle = null;
266
 
267
    /**
268
     * @var array
269
     */
270
    protected static $options = array(
271
        self::OPT_ACCEPTED_CHARSET_ENCODINGS,
272
        self::OPT_ACCEPTED_COMPRESSION,
273
        self::OPT_AUTH_TYPE,
274
        self::OPT_CA_CERT,
275
        self::OPT_CA_CERT_DIR,
276
        self::OPT_CERT,
277
        self::OPT_CERT_PASS,
278
        self::OPT_COOKIES,
279
        self::OPT_DEBUG,
280
        self::OPT_EXTRA_CURL_OPTS,
281
        self::OPT_EXTRA_SOCKET_OPTS,
282
        self::OPT_KEEPALIVE,
283
        self::OPT_KEY,
284
        self::OPT_KEY_PASS,
285
        self::OPT_NO_MULTICALL,
286
        self::OPT_PASSWORD,
287
        self::OPT_PROXY,
288
        self::OPT_PROXY_AUTH_TYPE,
289
        self::OPT_PROXY_PASS,
290
        self::OPT_PROXY_USER,
291
        self::OPT_PROXY_PORT,
292
        self::OPT_REQUEST_CHARSET_ENCODING,
293
        self::OPT_REQUEST_COMPRESSION,
294
        self::OPT_RETURN_TYPE,
295
        self::OPT_SSL_VERSION,
296
        self::OPT_TIMEOUT,
297
        self::OPT_USE_CURL,
298
        self::OPT_USER_AGENT,
299
        self::OPT_USERNAME,
300
        self::OPT_VERIFY_HOST,
301
        self::OPT_VERIFY_PEER,
302
    );
303
 
304
    /**
305
     * @param string $path either the PATH part of the xml-rpc server URL, or complete server URL (in which case you
306
     *                     should use an empty string for all other parameters)
307
     *                     e.g. /xmlrpc/server.php
308
     *                     e.g. http://phpxmlrpc.sourceforge.net/server.php
309
     *                     e.g. https://james:bond@secret.service.com:444/xmlrpcserver?agent=007
310
     *                     e.g. h2://fast-and-secure-services.org/endpoint
311
     * @param string $server the server name / ip address
312
     * @param integer $port the port the server is listening on, when omitted defaults to 80 or 443 depending on
313
     *                      protocol used
314
     * @param string $method the http protocol variant: defaults to 'http'; 'https', 'http11', 'h2' and 'h2c' can
315
     *                       be used if CURL is installed. The value set here can be overridden in any call to $this->send().
316
     *                       Use 'h2' to make the lib attempt to use http/2 over a secure connection, and 'h2c'
317
     *                       for http/2 without tls. Note that 'h2c' will not use the h2c 'upgrade' method, and be
318
     *                       thus incompatible with any server/proxy not supporting http/2. This is because POST
319
     *                       request are not compatible with h2c upgrade.
320
     */
321
    public function __construct($path, $server = '', $port = '', $method = '')
322
    {
323
        // allow user to specify all params in $path
324
        if ($server == '' && $port == '' && $method == '') {
325
            $parts = parse_url($path);
326
            $server = $parts['host'];
327
            $path = isset($parts['path']) ? $parts['path'] : '';
328
            if (isset($parts['query'])) {
329
                $path .= '?' . $parts['query'];
330
            }
331
            if (isset($parts['fragment'])) {
332
                $path .= '#' . $parts['fragment'];
333
            }
334
            if (isset($parts['port'])) {
335
                $port = $parts['port'];
336
            }
337
            if (isset($parts['scheme'])) {
338
                $method = $parts['scheme'];
339
            }
340
            if (isset($parts['user'])) {
341
                $this->username = $parts['user'];
342
            }
343
            if (isset($parts['pass'])) {
344
                $this->password = $parts['pass'];
345
            }
346
        }
347
        if ($path == '' || $path[0] != '/') {
348
            $this->path = '/' . $path;
349
        } else {
350
            $this->path = $path;
351
        }
352
        $this->server = $server;
353
        if ($port != '') {
354
            $this->port = $port;
355
        }
356
        if ($method != '') {
357
            $this->method = $method;
358
        }
359
 
360
        // if ZLIB is enabled, let the client by default accept compressed responses
361
        if (function_exists('gzinflate') || (
362
                function_exists('curl_version') && (($info = curl_version()) &&
363
                    ((is_string($info) && strpos($info, 'zlib') !== null) || isset($info['libz_version'])))
364
            )
365
        ) {
366
            $this->accepted_compression = array('gzip', 'deflate');
367
        }
368
 
369
        // keepalives: enabled by default
370
        $this->keepalive = true;
371
 
372
        // by default the xml parser can support these 3 charset encodings
373
        $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII');
374
 
375
        // NB: this is disabled to avoid making all the requests sent huge... mbstring supports more than 80 charsets!
376
        //$ch = $this->getCharsetEncoder();
377
        //$this->accepted_charset_encodings = $ch->knownCharsets();
378
 
379
        // initialize user_agent string
380
        $this->user_agent = PhpXmlRpc::$xmlrpcName . ' ' . PhpXmlRpc::$xmlrpcVersion;
381
    }
382
 
383
    /**
384
     * @param string $name see all the OPT_ constants
385
     * @param mixed $value
386
     * @return $this
387
     * @throws ValueErrorException on unsupported option
388
     */
389
    public function setOption($name, $value)
390
    {
391
        if (in_array($name, static::$options)) {
392
            $this->$name = $value;
393
            return $this;
394
        }
395
 
396
        throw new ValueErrorException("Unsupported option '$name'");
397
    }
398
 
399
    /**
400
     * @param string $name see all the OPT_ constants
401
     * @return mixed
402
     * @throws ValueErrorException on unsupported option
403
     */
404
    public function getOption($name)
405
    {
406
        if (in_array($name, static::$options)) {
407
            return $this->$name;
408
        }
409
 
410
        throw new ValueErrorException("Unsupported option '$name'");
411
    }
412
 
413
    /**
414
     * Returns the complete list of Client options, with their value.
415
     * @return array
416
     */
417
    public function getOptions()
418
    {
419
        $values = array();
420
        foreach (static::$options as $opt) {
421
            $values[$opt] = $this->getOption($opt);
422
        }
423
        return $values;
424
    }
425
 
426
    /**
427
     * @param array $options key: any valid option (see all the OPT_ constants)
428
     * @return $this
429
     * @throws ValueErrorException on unsupported option
430
     */
431
    public function setOptions($options)
432
    {
433
        foreach ($options as $name => $value) {
434
            $this->setOption($name, $value);
435
        }
436
 
437
        return $this;
438
    }
439
 
440
    /**
441
     * Enable/disable the echoing to screen of the xml-rpc responses received. The default is not to output anything.
442
     *
443
     * The debugging information at level 1 includes the raw data returned from the XML-RPC server it was querying
444
     * (including bot HTTP headers and the full XML payload), and the PHP value the client attempts to create to
445
     * represent the value returned by the server.
446
     * At level 2, the complete payload of the xml-rpc request is also printed, before being sent to the server.
447
     * At level -1, the Response objects returned by send() calls will not carry information about the http response's
448
     * cookies, headers and body, which might save some memory
449
     *
450
     * This option can be very useful when debugging servers as it allows you to see exactly what the client sends and
451
     * the server returns. Never leave it enabled for production!
452
     *
453
     * @param integer $level values -1, 0, 1 and 2 are supported
454
     * @return $this
455
     */
456
    public function setDebug($level)
457
    {
458
        $this->debug = $level;
459
        return $this;
460
    }
461
 
462
    /**
463
     * Sets the username and password for authorizing the client to the server.
464
     *
465
     * With the default (HTTP) transport, this information is used for HTTP Basic authorization.
466
     * Note that username and password can also be set using the class constructor.
467
     * With HTTP 1.1 and HTTPS transport, NTLM and Digest authentication protocols are also supported. To enable them use
468
     * the constants CURLAUTH_DIGEST and CURLAUTH_NTLM as values for the auth type parameter.
469
     *
470
     * @param string $user username
471
     * @param string $password password
472
     * @param integer $authType auth type. See curl_setopt man page for supported auth types. Defaults to CURLAUTH_BASIC
473
     *                          (basic auth). Note that auth types NTLM and Digest will only work if the Curl php
474
     *                          extension is enabled.
475
     * @return $this
476
     */
477
    public function setCredentials($user, $password, $authType = 1)
478
    {
479
        $this->username = $user;
480
        $this->password = $password;
481
        $this->authtype = $authType;
482
        return $this;
483
    }
484
 
485
    /**
486
     * Set the optional certificate and passphrase used in SSL-enabled communication with a remote server.
487
     *
488
     * Note: to retrieve information about the client certificate on the server side, you will need to look into the
489
     * environment variables which are set up by the webserver. Different webservers will typically set up different
490
     * variables.
491
     *
492
     * @param string $cert the name of a file containing a PEM formatted certificate
493
     * @param string $certPass the password required to use it
494
     * @return $this
495
     */
496
    public function setCertificate($cert, $certPass = '')
497
    {
498
        $this->cert = $cert;
499
        $this->certpass = $certPass;
500
        return $this;
501
    }
502
 
503
    /**
504
     * Add a CA certificate to verify server with in SSL-enabled communication when SetSSLVerifypeer has been set to TRUE.
505
     *
506
     * See the php manual page about CURLOPT_CAINFO for more details.
507
     *
508
     * @param string $caCert certificate file name (or dir holding certificates)
509
     * @param bool $isDir set to true to indicate cacert is a dir. defaults to false
510
     * @return $this
511
     */
512
    public function setCaCertificate($caCert, $isDir = false)
513
    {
514
        if ($isDir) {
515
            $this->cacertdir = $caCert;
516
        } else {
517
            $this->cacert = $caCert;
518
        }
519
        return $this;
520
    }
521
 
522
    /**
523
     * Set attributes for SSL communication: private SSL key.
524
     *
525
     * NB: does not work in older php/curl installs.
526
     * Thanks to Daniel Convissor.
527
     *
528
     * @param string $key The name of a file containing a private SSL key
529
     * @param string $keyPass The secret password needed to use the private SSL key
530
     * @return $this
531
     */
532
    public function setKey($key, $keyPass)
533
    {
534
        $this->key = $key;
535
        $this->keypass = $keyPass;
536
        return $this;
537
    }
538
 
539
    /**
540
     * Set attributes for SSL communication: verify the remote host's SSL certificate, and cause the connection to fail
541
     * if the cert verification fails.
542
     *
543
     * By default, verification is enabled.
544
     * To specify custom SSL certificates to validate the server with, use the setCaCertificate method.
545
     *
546
     * @param bool $i enable/disable verification of peer certificate
547
     * @return $this
548
     * @deprecated use setOption
549
     */
550
    public function setSSLVerifyPeer($i)
551
    {
552
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
553
 
554
        $this->verifypeer = $i;
555
        return $this;
556
    }
557
 
558
    /**
559
     * Set attributes for SSL communication: verify the remote host's SSL certificate's common name (CN).
560
     *
561
     * Note that support for value 1 has been removed in cURL 7.28.1
562
     *
563
     * @param int $i Set to 1 to only the existence of a CN, not that it matches
564
     * @return $this
565
     * @deprecated use setOption
566
     */
567
    public function setSSLVerifyHost($i)
568
    {
569
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
570
 
571
        $this->verifyhost = $i;
572
        return $this;
573
    }
574
 
575
    /**
576
     * Set attributes for SSL communication: SSL version to use. Best left at 0 (default value): let cURL decide
577
     *
578
     * @param int $i see  CURL_SSLVERSION_ constants
579
     * @return $this
580
     * @deprecated use setOption
581
     */
582
    public function setSSLVersion($i)
583
    {
584
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
585
 
586
        $this->sslversion = $i;
587
        return $this;
588
    }
589
 
590
    /**
591
     * Set proxy info.
592
     *
593
     * NB: CURL versions before 7.11.10 cannot use a proxy to communicate with https servers.
594
     *
595
     * @param string $proxyHost
596
     * @param string $proxyPort Defaults to 8080 for HTTP and 443 for HTTPS
597
     * @param string $proxyUsername Leave blank if proxy has public access
598
     * @param string $proxyPassword Leave blank if proxy has public access
599
     * @param int $proxyAuthType defaults to CURLAUTH_BASIC (Basic authentication protocol); set to constant CURLAUTH_NTLM
600
     *                           to use NTLM auth with proxy (has effect only when the client uses the HTTP 1.1 protocol)
601
     * @return $this
602
     */
603
    public function setProxy($proxyHost, $proxyPort, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1)
604
    {
605
        $this->proxy = $proxyHost;
606
        $this->proxyport = $proxyPort;
607
        $this->proxy_user = $proxyUsername;
608
        $this->proxy_pass = $proxyPassword;
609
        $this->proxy_authtype = $proxyAuthType;
610
        return $this;
611
    }
612
 
613
    /**
614
     * Enables/disables reception of compressed xml-rpc responses.
615
     *
616
     * This requires the "zlib" extension to be enabled in your php install. If it is, by default xmlrpc_client
617
     * instances will enable reception of compressed content.
618
     * Note that enabling reception of compressed responses merely adds some standard http headers to xml-rpc requests.
619
     * It is up to the xml-rpc server to return compressed responses when receiving such requests.
620
     *
621
     * @param string $compMethod either 'gzip', 'deflate', 'any' or ''
622
     * @return $this
623
     */
624
    public function setAcceptedCompression($compMethod)
625
    {
626
        if ($compMethod == 'any') {
627
            $this->accepted_compression = array('gzip', 'deflate');
628
        } elseif ($compMethod == false) {
629
            $this->accepted_compression = array();
630
        } else {
631
            $this->accepted_compression = array($compMethod);
632
        }
633
        return $this;
634
    }
635
 
636
    /**
637
     * Enables/disables http compression of xml-rpc request.
638
     *
639
     * This requires the "zlib" extension to be enabled in your php install.
640
     * Take care when sending compressed requests: servers might not support them (and automatic fallback to
641
     * uncompressed requests is not yet implemented).
642
     *
643
     * @param string $compMethod either 'gzip', 'deflate' or ''
644
     * @return $this
645
     * @deprecated use setOption
646
     */
647
    public function setRequestCompression($compMethod)
648
    {
649
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
650
 
651
        $this->request_compression = $compMethod;
652
        return $this;
653
    }
654
 
655
    /**
656
     * Adds a cookie to list of cookies that will be sent to server with every further request (useful e.g. for keeping
657
     * session info outside the xml-rpc payload).
658
     *
659
     * NB: by default all cookies set via this method are sent to the server, regardless of path/domain/port. Taking
660
     * advantage of those values is left to the single developer.
661
     *
662
     * @param string $name nb: will not be escaped in the request's http headers. Take care not to use CTL chars or
663
     *                     separators!
664
     * @param string $value
665
     * @param string $path
666
     * @param string $domain
667
     * @param int $port do not use! Cookies are not separated by port
668
     * @return $this
669
     *
670
     * @todo check correctness of urlencoding cookie value (copied from php way of doing it, but php is generally sending
671
     *       response not requests. We do the opposite...)
672
     * @todo strip invalid chars from cookie name? As per RFC6265, we should follow RFC2616, Section 2.2
673
     * @todo drop/rename $port parameter. Cookies are not isolated by port!
674
     * @todo feature-creep allow storing 'expires', 'secure', 'httponly' and 'samesite' cookie attributes (we could do
675
     *       as php, and allow $path to be an array of attributes...)
676
     */
677
    public function setCookie($name, $value = '', $path = '', $domain = '', $port = null)
678
    {
679
        $this->cookies[$name]['value'] = rawurlencode($value);
680
        if ($path || $domain || $port) {
681
            $this->cookies[$name]['path'] = $path;
682
            $this->cookies[$name]['domain'] = $domain;
683
            $this->cookies[$name]['port'] = $port;
684
        }
685
        return $this;
686
    }
687
 
688
    /**
689
     * Directly set cURL options, for extra flexibility (when in cURL mode).
690
     *
691
     * It allows e.g. to bind client to a specific IP interface / address.
692
     *
693
     * @param array $options
694
     * @return $this
695
     * @deprecated use setOption
696
     */
697
    public function setCurlOptions($options)
698
    {
699
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
700
 
701
        $this->extracurlopts = $options;
702
        return $this;
703
    }
704
 
705
    /**
706
     * @param int $useCurlMode self::USE_CURL_ALWAYS, self::USE_CURL_AUTO or self::USE_CURL_NEVER
707
     * @return $this
708
     * @deprecated use setOption
709
     */
710
    public function setUseCurl($useCurlMode)
711
    {
712
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
713
 
714
        $this->use_curl = $useCurlMode;
715
        return $this;
716
    }
717
 
718
 
719
    /**
720
     * Set user-agent string that will be used by this client instance in http headers sent to the server.
721
     *
722
     * The default user agent string includes the name of this library and the version number.
723
     *
724
     * @param string $agentString
725
     * @return $this
726
     * @deprecated use setOption
727
     */
728
    public function setUserAgent($agentString)
729
    {
730
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
731
 
732
        $this->user_agent = $agentString;
733
        return $this;
734
    }
735
 
736
    /**
737
     * @param null|int $component allowed values: PHP_URL_SCHEME, PHP_URL_HOST, PHP_URL_PORT, PHP_URL_PATH
738
     * @return string|int Notes: the path component will include query string and fragment; NULL is a valid value for port
739
     *                    (in which case the default port for http/https will be used);
740
     * @throws ValueErrorException on unsupported component
741
     */
742
    public function getUrl($component = null)
743
    {
744
        if (is_int($component) || ctype_digit($component)) {
745
            switch ($component) {
746
                case PHP_URL_SCHEME:
747
                    return $this->method;
748
                case PHP_URL_HOST:
749
                    return $this->server;
750
                case PHP_URL_PORT:
751
                    return $this->port;
752
                case  PHP_URL_PATH:
753
                    return $this->path;
754
                case '':
755
 
756
                default:
757
                    throw new ValueErrorException("Unsupported component '$component'");
758
            }
759
        }
760
 
761
        $url = $this->method . '://' . $this->server;
762
        if ($this->port == 0 || ($this->port == 80 && in_array($this->method, array('http', 'http10', 'http11', 'h2c'))) ||
763
            ($this->port == 443 && in_array($this->method, array('https', 'h2')))) {
764
            return $url . $this->path;
765
        } else {
766
            return $url . ':' . $this->port . $this->path;
767
        }
768
    }
769
 
770
    /**
771
     * Send an xml-rpc request to the server.
772
     *
773
     * @param Request|Request[]|string $req The Request object, or an array of requests for using multicall, or the
774
     *                                      complete xml representation of a request.
775
     *                                      When sending an array of Request objects, the client will try to make use of
776
     *                                      a single 'system.multicall' xml-rpc method call to forward to the server all
777
     *                                      the requests in a single HTTP round trip, unless $this->no_multicall has
778
     *                                      been previously set to TRUE (see the multicall method below), in which case
779
     *                                      many consecutive xml-rpc requests will be sent. The method will return an
780
     *                                      array of Response objects in both cases.
781
     *                                      The third variant allows to build by hand (or any other means) a complete
782
     *                                      xml-rpc request message, and send it to the server. $req should be a string
783
     *                                      containing the complete xml representation of the request. It is e.g. useful
784
     *                                      when, for maximal speed of execution, the request is serialized into a
785
     *                                      string using the native php xml-rpc functions (see http://www.php.net/xmlrpc)
786
     * @param integer $timeout deprecated. Connection timeout, in seconds, If unspecified, the timeout set with setOption
787
     *                         will be used. If that is 0, a platform specific timeout will apply.
788
     *                         This timeout value is passed to fsockopen(). It is also used for detecting server
789
     *                         timeouts during communication (i.e. if the server does not send anything to the client
790
     *                         for $timeout seconds, the connection will be closed).
791
     * @param string $method deprecated. Use the same value in the constructor instead.
792
     *                       Valid values are 'http', 'http11', 'https', 'h2' and 'h2c'. If left empty,
793
     *                       the http protocol chosen during creation of the object will be used.
794
     *                       Use 'h2' to make the lib attempt to use http/2 over a secure connection, and 'h2c'
795
     *                       for http/2 without tls. Note that 'h2c' will not use the h2c 'upgrade' method, and be
796
     *                       thus incompatible with any server/proxy not supporting http/2. This is because POST
797
     *                       request are not compatible with h2c upgrade.
798
     * @return Response|Response[] Note that the client will always return a Response object, even if the call fails
799
     *
800
     * @todo allow throwing exceptions instead of returning responses in case of failed calls and/or Fault responses
801
     * @todo refactor: we now support many options besides connection timeout and http version to use. Why only privilege those?
802
     */
803
    public function send($req, $timeout = 0, $method = '')
804
    {
805
        if ($method !== '' || $timeout !== 0) {
806
            $this->logDeprecation("Using non-default values for arguments 'method' and 'timeout' when calling method " . __METHOD__ . ' is deprecated');
807
        }
808
 
809
        // if user does not specify http protocol, use native method of this client
810
        // (i.e. method set during call to constructor)
811
        if ($method == '') {
812
            $method = $this->method;
813
        }
814
 
815
        if ($timeout == 0) {
816
            $timeout = $this->timeout;
817
        }
818
 
819
        if (is_array($req)) {
820
            // $req is an array of Requests
821
            /// @todo switch to the new syntax for multicall
822
            return $this->multicall($req, $timeout, $method);
823
        } elseif (is_string($req)) {
824
            $n = new static::$requestClass('');
825
            $n->setPayload($req);
826
            $req = $n;
827
        }
828
 
829
        // where req is a Request
830
        $req->setDebug($this->debug);
831
 
832
        /// @todo we could be smarter about this and not force usage of curl for https if not present as well as use the
833
        ///       presence of curl_extra_opts or socket_extra_opts as a hint
834
        $useCurl = ($this->use_curl == self::USE_CURL_ALWAYS) || ($this->use_curl == self::USE_CURL_AUTO && (
835
            in_array($method, array('https', 'http11', 'h2c', 'h2')) ||
836
            ($this->username != '' && $this->authtype != 1) ||
837
            ($this->proxy != '' && $this->proxy_user != '' && $this->proxy_authtype != 1)
838
        ));
839
 
840
        // BC - we go through sendPayloadCURL/sendPayloadSocket in case some subclass reimplemented those
841
        if ($useCurl) {
842
            $r = $this->sendPayloadCURL(
843
                $req,
844
                $this->server,
845
                $this->port,
846
                $timeout,
847
                $this->username,
848
                $this->password,
849
                $this->authtype,
850
                $this->cert,
851
                $this->certpass,
852
                $this->cacert,
853
                $this->cacertdir,
854
                $this->proxy,
855
                $this->proxyport,
856
                $this->proxy_user,
857
                $this->proxy_pass,
858
                $this->proxy_authtype,
859
                // BC
860
                $method == 'http11' ? 'http' : $method,
861
                $this->keepalive,
862
                $this->key,
863
                $this->keypass,
864
                $this->sslversion
865
            );
866
        } else {
867
            $r = $this->sendPayloadSocket(
868
                $req,
869
                $this->server,
870
                $this->port,
871
                $timeout,
872
                $this->username,
873
                $this->password,
874
                $this->authtype,
875
                $this->cert,
876
                $this->certpass,
877
                $this->cacert,
878
                $this->cacertdir,
879
                $this->proxy,
880
                $this->proxyport,
881
                $this->proxy_user,
882
                $this->proxy_pass,
883
                $this->proxy_authtype,
884
                $method,
885
                $this->key,
886
                $this->keypass,
887
                $this->sslversion
888
            );
889
        }
890
 
891
        return $r;
892
    }
893
 
894
    /**
895
     * @param Request $req
896
     * @param string $method
897
     * @param string $server
898
     * @param int $port
899
     * @param string $path
900
     * @param array $opts
901
     * @return Response
902
     */
903
    protected function sendViaSocket($req, $method, $server, $port, $path, $opts)
904
    {
905
        /// @todo log a warning if passed an unsupported method
906
 
907
        // Only create the payload if it was not created previously
908
        /// @todo what if the request's payload was created with a different encoding?
909
        ///       Also, if we do not call serialize(), the request will not set its content-type to have the charset declared
910
        $payload = $req->getPayload();
911
        if (empty($payload)) {
912
            $payload = $req->serialize($opts['request_charset_encoding']);
913
        }
914
 
915
        // Deflate request body and set appropriate request headers
916
        $encodingHdr = '';
917
        if ($opts['request_compression'] == 'gzip' || $opts['request_compression'] == 'deflate') {
918
            if ($opts['request_compression'] == 'gzip' && function_exists('gzencode')) {
919
                $a = @gzencode($payload);
920
                if ($a) {
921
                    $payload = $a;
922
                    $encodingHdr = "Content-Encoding: gzip\r\n";
923
                } else {
924
                    $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzencode failure in compressing request');
925
                }
926
            } else if (function_exists('gzcompress')) {
927
                $a = @gzcompress($payload);
928
                if ($a) {
929
                    $payload = $a;
930
                    $encodingHdr = "Content-Encoding: deflate\r\n";
931
                } else {
932
                    $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzcompress failure in compressing request');
933
                }
934
            } else {
935
                $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported by this PHP install');
936
            }
937
        } else {
938
            if ($opts['request_compression'] != '') {
939
                $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported');
940
            }
941
        }
942
 
943
        // thanks to Grant Rauscher
944
        $credentials = '';
945
        if ($opts['username'] != '') {
946
            if ($opts['authtype'] != 1) {
947
                $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported with HTTP 1.0');
948
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
949
                    PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': only Basic auth is supported with HTTP 1.0');
950
            }
951
            $credentials = 'Authorization: Basic ' . base64_encode($opts['username'] . ':' . $opts['password']) . "\r\n";
952
        }
953
 
954
        $acceptedEncoding = '';
955
        if (is_array($opts['accepted_compression']) && count($opts['accepted_compression'])) {
956
            $acceptedEncoding = 'Accept-Encoding: ' . implode(', ', $opts['accepted_compression']) . "\r\n";
957
        }
958
 
959
        if ($port == 0) {
960
            $port = ($method === 'https') ? 443 : 80;
961
        }
962
 
963
        $proxyCredentials = '';
964
        if ($opts['proxy']) {
965
            if ($opts['proxyport'] == 0) {
966
                $opts['proxyport'] = 8080;
967
            }
968
            $connectServer = $opts['proxy'];
969
            $connectPort = $opts['proxyport'];
970
            $transport = 'tcp';
971
            /// @todo check: should we not use https in some cases?
972
            $uri = 'http://' . $server . ':' . $port . $path;
973
            if ($opts['proxy_user'] != '') {
974
                if ($opts['proxy_authtype'] != 1) {
975
                    $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported with HTTP 1.0');
976
                    return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
977
                        PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': only Basic auth to proxy is supported with HTTP 1.0');
978
                }
979
                $proxyCredentials = 'Proxy-Authorization: Basic ' . base64_encode($opts['proxy_user'] . ':' .
980
                    $opts['proxy_pass']) . "\r\n";
981
            }
982
        } else {
983
            $connectServer = $server;
984
            $connectPort = $port;
985
            $transport = ($method === 'https') ? 'tls' : 'tcp';
986
            $uri = $path;
987
        }
988
 
989
        // Cookie generation, as per RFC6265
990
        // NB: the following code does not honour 'expires', 'path' and 'domain' cookie attributes set to client obj by the user...
991
        $cookieHeader = '';
992
        if (count($opts['cookies'])) {
993
            $version = '';
994
            foreach ($opts['cookies'] as $name => $cookie) {
995
                /// @todo should we sanitize the cookie value on behalf of the user? See setCookie comments
996
                $cookieHeader .= ' ' . $name . '=' . $cookie['value'] . ";";
997
            }
998
            $cookieHeader = 'Cookie:' . $version . substr($cookieHeader, 0, -1) . "\r\n";
999
        }
1000
 
1001
        // omit port if default
1002
        if (($port == 80 && in_array($method, array('http', 'http10'))) || ($port == 443 && $method == 'https')) {
1003
            $port = '';
1004
        } else {
1005
            $port = ':' . $port;
1006
        }
1007
 
1008
        $op = 'POST ' . $uri . " HTTP/1.0\r\n" .
1009
            'User-Agent: ' . $opts['user_agent'] . "\r\n" .
1010
            'Host: ' . $server . $port . "\r\n" .
1011
            $credentials .
1012
            $proxyCredentials .
1013
            $acceptedEncoding .
1014
            $encodingHdr .
1015
            'Accept-Charset: ' . implode(',', $opts['accepted_charset_encodings']) . "\r\n" .
1016
            $cookieHeader .
1017
            'Content-Type: ' . $req->getContentType() . "\r\nContent-Length: " .
1018
            strlen($payload) . "\r\n\r\n" .
1019
            $payload;
1020
 
1021
        if ($opts['debug'] > 1) {
1022
            $this->getLogger()->debug("---SENDING---\n$op\n---END---");
1023
        }
1024
 
1025
        $contextOptions = array();
1026
        if ($method == 'https') {
1027
            if ($opts['cert'] != '') {
1028
                $contextOptions['ssl']['local_cert'] = $opts['cert'];
1029
                if ($opts['certpass'] != '') {
1030
                    $contextOptions['ssl']['passphrase'] = $opts['certpass'];
1031
                }
1032
            }
1033
            if ($opts['cacert'] != '') {
1034
                $contextOptions['ssl']['cafile'] = $opts['cacert'];
1035
            }
1036
            if ($opts['cacertdir'] != '') {
1037
                $contextOptions['ssl']['capath'] = $opts['cacertdir'];
1038
            }
1039
            if ($opts['key'] != '') {
1040
                $contextOptions['ssl']['local_pk'] = $opts['key'];
1041
            }
1042
            $contextOptions['ssl']['verify_peer'] = $opts['verifypeer'];
1043
            $contextOptions['ssl']['verify_peer_name'] = $opts['verifypeer'];
1044
 
1045
            if ($opts['sslversion'] != 0) {
1046
                /// @see https://www.php.net/manual/en/function.curl-setopt.php, https://www.php.net/manual/en/migration56.openssl.php
1047
                switch($opts['sslversion']) {
1048
                    /// @todo what does this map to? 1.0-1.3?
1049
                    //case 1: // TLSv1
1050
                    //    break;
1051
                    case 2: // SSLv2
1052
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_SSLv2_CLIENT;
1053
                        break;
1054
                    case 3: // SSLv3
1055
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_SSLv3_CLIENT;
1056
                        break;
1057
                    case 4: // TLSv1.0
1058
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT;
1059
                        break;
1060
                    case 5: // TLSv1.1
1061
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
1062
                        break;
1063
                    case 6: // TLSv1.2
1064
                        $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
1065
                        break;
1066
                    case 7: // TLSv1.3
1067
                        if (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT')) {
1068
                            $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT;
1069
                        } else {
1070
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
1071
                                PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': TLS-1.3 only is supported with PHP 7.4 or later');
1072
                        }
1073
                        break;
1074
                    default:
1075
                        return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['unsupported_option'],
1076
                            PhpXmlRpc::$xmlrpcerr['unsupported_option'] . ': Unsupported required TLS version');
1077
                }
1078
            }
1079
        }
1080
 
1081
        foreach ($opts['extracurlopts'] as $proto => $protoOpts) {
1082
            foreach ($protoOpts as $key => $val) {
1083
                $contextOptions[$proto][$key] = $val;
1084
            }
1085
        }
1086
 
1087
        $context = stream_context_create($contextOptions);
1088
 
1089
        if ($opts['timeout'] <= 0) {
1090
            $connectTimeout = ini_get('default_socket_timeout');
1091
        } else {
1092
            $connectTimeout = $opts['timeout'];
1093
        }
1094
 
1095
        $this->errno = 0;
1096
        $this->errstr = '';
1097
 
1098
        $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr, $connectTimeout,
1099
            STREAM_CLIENT_CONNECT, $context);
1100
        if ($fp) {
1101
            if ($opts['timeout'] > 0) {
1102
                stream_set_timeout($fp, $opts['timeout'], 0);
1103
            }
1104
        } else {
1105
            if ($this->errstr == '') {
1106
                $err = error_get_last();
1107
                $this->errstr = $err['message'];
1108
            }
1109
 
1110
            $this->errstr = 'Connect error: ' . $this->errstr;
1111
            $r = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr . ' (' . $this->errno . ')');
1112
 
1113
            return $r;
1114
        }
1115
 
1116
        if (!fputs($fp, $op, strlen($op))) {
1117
            fclose($fp);
1118
            $this->errstr = 'Write error';
1119
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr);
1120
        }
1121
 
1122
        // Close socket before parsing.
1123
        // It should yield slightly better execution times, and make easier recursive calls (e.g. to follow http redirects)
1124
        $ipd = '';
1125
        do {
1126
            // shall we check for $data === FALSE?
1127
            // as per the manual, it signals an error
1128
            $ipd .= fread($fp, 32768);
1129
        } while (!feof($fp));
1130
        fclose($fp);
1131
 
1132
        return $req->parseResponse($ipd, false, $opts['return_type']);
1133
    }
1134
 
1135
    /**
1136
     * Contributed by Justin Miller
1137
     * Requires curl to be built into PHP
1138
     * NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers!
1139
     *
1140
     * @param Request $req
1141
     * @param string $method
1142
     * @param string $server
1143
     * @param int $port
1144
     * @param string $path
1145
     * @param array $opts the keys/values match self::getOptions
1146
     * @return Response
1147
     *
1148
     * @todo the $path arg atm is ignored. What to do if it is != $this->path?
1149
     */
1150
    protected function sendViaCURL($req, $method, $server, $port, $path, $opts)
1151
    {
1152
        if (!function_exists('curl_init')) {
1153
            $this->errstr = 'CURL unavailable on this install';
1154
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_curl'], PhpXmlRpc::$xmlrpcstr['no_curl']);
1155
        }
1156
        if ($method == 'https' || $method == 'h2') {
1157
            // q: what about installs where we get back a string, but curl is linked to other ssl libs than openssl?
1158
            if (($info = curl_version()) &&
1159
                ((is_string($info) && strpos($info, 'OpenSSL') === null) || (is_array($info) && !isset($info['ssl_version'])))
1160
            ) {
1161
                $this->errstr = 'SSL unavailable on this install';
1162
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_ssl'], PhpXmlRpc::$xmlrpcstr['no_ssl']);
1163
            }
1164
        }
1165
        if (($method == 'h2' && !defined('CURL_HTTP_VERSION_2_0')) ||
1166
            ($method == 'h2c' && !defined('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE'))) {
1167
            $this->errstr = 'HTTP/2 unavailable on this install';
1168
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_http2'], PhpXmlRpc::$xmlrpcstr['no_http2']);
1169
        }
1170
 
1171
        // BC - we go through prepareCurlHandle in case some subclass reimplemented it
1172
        $curl = $this->prepareCurlHandle($req, $server, $port, $opts['timeout'], $opts['username'], $opts['password'],
1173
            $opts['authtype'], $opts['cert'], $opts['certpass'], $opts['cacert'], $opts['cacertdir'], $opts['proxy'],
1174
            $opts['proxyport'], $opts['proxy_user'], $opts['proxy_pass'], $opts['proxy_authtype'], $method,
1175
            $opts['keepalive'], $opts['key'], $opts['keypass'], $opts['sslversion']);
1176
 
1177
        if (!$curl) {
1178
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] .
1179
                ': error during curl initialization. Check php error log for details');
1180
        }
1181
 
1182
        $result = curl_exec($curl);
1183
 
1184
        if ($opts['debug'] > 1) {
1185
            $message = "---CURL INFO---\n";
1186
            foreach (curl_getinfo($curl) as $name => $val) {
1187
                if (is_array($val)) {
1188
                    $val = implode("\n", $val);
1189
                }
1190
                $message .= $name . ': ' . $val . "\n";
1191
            }
1192
            $message .= '---END---';
1193
            $this->getLogger()->debug($message);
1194
        }
1195
 
1196
        if (!$result) {
1197
            /// @todo we should use a better check here - what if we get back '' or '0'?
1198
 
1199
            $this->errstr = 'no response';
1200
            $resp = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] .
1201
                ': ' . curl_error($curl));
1202
            curl_close($curl);
1203
            if ($opts['keepalive']) {
1204
                $this->xmlrpc_curl_handle = null;
1205
            }
1206
        } else {
1207
            if (!$opts['keepalive']) {
1208
                curl_close($curl);
1209
            }
1210
            $resp = $req->parseResponse($result, true, $opts['return_type']);
1211
            if ($opts['keepalive']) {
1212
                /// @todo if we got back a 302 or 308, we should not reuse the curl handle for later calls
1213
                if ($resp->faultCode() == PhpXmlRpc::$xmlrpcerr['http_error']) {
1214
                    curl_close($curl);
1215
                    $this->xmlrpc_curl_handle = null;
1216
                }
1217
            }
1218
        }
1219
 
1220
        return $resp;
1221
    }
1222
 
1223
    /**
1224
     * @param Request $req
1225
     * @param string $method
1226
     * @param string $server
1227
     * @param int $port
1228
     * @param string $path
1229
     * @param array $opts the keys/values match self::getOptions
1230
     * @return \CurlHandle|resource|false
1231
     *
1232
     * @todo allow this method to either throw or return a Response, so that we can pass back to caller more info on errors
1233
     */
1234
    protected function createCURLHandle($req, $method, $server, $port, $path, $opts)
1235
    {
1236
        if ($port == 0) {
1237
            if (in_array($method, array('http', 'http10', 'http11', 'h2c'))) {
1238
                $port = 80;
1239
            } else {
1240
                $port = 443;
1241
            }
1242
        }
1243
 
1244
        // Only create the payload if it was not created previously
1245
        $payload = $req->getPayload();
1246
        if (empty($payload)) {
1247
            $payload = $req->serialize($opts['request_charset_encoding']);
1248
        }
1249
 
1250
        // Deflate request body and set appropriate request headers
1251
        $encodingHdr = '';
1252
        if (($opts['request_compression'] == 'gzip' || $opts['request_compression'] == 'deflate')) {
1253
            if ($opts['request_compression'] == 'gzip' && function_exists('gzencode')) {
1254
                $a = @gzencode($payload);
1255
                if ($a) {
1256
                    $payload = $a;
1257
                    $encodingHdr = 'Content-Encoding: gzip';
1258
                } else {
1259
                    $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzencode failure in compressing request');
1260
                }
1261
            } else if (function_exists('gzcompress')) {
1262
                $a = @gzcompress($payload);
1263
                if ($a) {
1264
                    $payload = $a;
1265
                    $encodingHdr = 'Content-Encoding: deflate';
1266
                } else {
1267
                    $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': gzcompress failure in compressing request');
1268
                }
1269
            } else {
1270
                $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported by this PHP install');
1271
            }
1272
        } else {
1273
            if ($opts['request_compression'] != '') {
1274
                $this->getLogger()->warning('XML-RPC: ' . __METHOD__ . ': desired request compression method is unsupported');
1275
            }
1276
        }
1277
 
1278
        if (!$opts['keepalive'] || !$this->xmlrpc_curl_handle) {
1279
            if ($method == 'http11' || $method == 'http10' || $method == 'h2c') {
1280
                $protocol = 'http';
1281
            } else {
1282
                if ($method == 'h2') {
1283
                    $protocol = 'https';
1284
                } else {
1285
                    // http, https
1286
                    $protocol = $method;
1287
                    if (strpos($protocol, ':') !== false) {
1288
                        $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ": warning - attempted hacking attempt?. The curl protocol requested for the call is: '$protocol'");
1289
                        return false;
1290
                    }
1291
                }
1292
            }
1293
            $curl = curl_init($protocol . '://' . $server . ':' . $port . $path);
1294
            if (!$curl) {
1295
                return false;
1296
            }
1297
            if ($opts['keepalive']) {
1298
                $this->xmlrpc_curl_handle = $curl;
1299
            }
1300
        } else {
1301
            $curl = $this->xmlrpc_curl_handle;
1302
        }
1303
 
1304
        // results into variable
1305
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
1306
 
1307
        if ($opts['debug'] > 1) {
1308
            curl_setopt($curl, CURLOPT_VERBOSE, true);
1309
            /// @todo redirect curlopt_stderr to some stream which can be piped to the logger
1310
        }
1311
        curl_setopt($curl, CURLOPT_USERAGENT, $opts['user_agent']);
1312
        // required for XMLRPC: post the data
1313
        curl_setopt($curl, CURLOPT_POST, 1);
1314
        // the data
1315
        curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
1316
 
1317
        // return the header too
1318
        curl_setopt($curl, CURLOPT_HEADER, 1);
1319
 
1320
        // NB: if we set an empty string, CURL will add http header indicating
1321
        // ALL methods it is supporting. This is possibly a better option than letting the user tell what curl can / cannot do...
1322
        if (is_array($opts['accepted_compression']) && count($opts['accepted_compression'])) {
1323
            //curl_setopt($curl, CURLOPT_ENCODING, implode(',', $opts['accepted_compression']));
1324
            // empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1325
            if (count($opts['accepted_compression']) == 1) {
1326
                curl_setopt($curl, CURLOPT_ENCODING, $opts['accepted_compression'][0]);
1327
            } else {
1328
                curl_setopt($curl, CURLOPT_ENCODING, '');
1329
            }
1330
        }
1331
        // extra headers
1332
        $headers = array('Content-Type: ' . $req->getContentType(), 'Accept-Charset: ' . implode(',', $opts['accepted_charset_encodings']));
1333
        // if no keepalive is wanted, let the server know it in advance
1334
        if (!$opts['keepalive']) {
1335
            $headers[] = 'Connection: close';
1336
        }
1337
        // request compression header
1338
        if ($encodingHdr) {
1339
            $headers[] = $encodingHdr;
1340
        }
1341
 
1342
        // Fix the HTTP/1.1 417 Expectation Failed Bug (curl by default adds a 'Expect: 100-continue' header when POST
1343
        // size exceeds 1025 bytes, apparently)
1344
        $headers[] = 'Expect:';
1345
 
1346
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
1347
        // timeout is borked
1348
        if ($opts['timeout']) {
1349
            curl_setopt($curl, CURLOPT_TIMEOUT, $opts['timeout'] == 1 ? 1 : $opts['timeout'] - 1);
1350
        }
1351
 
1352
        switch ($method) {
1353
            case 'http10':
1354
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
1355
                break;
1356
            case 'http11':
1357
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
1358
                break;
1359
            case 'h2c':
1360
                if (defined('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE')) {
1361
                    curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE);
1362
                } else {
1363
                    $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. HTTP2 is not supported by the current PHP/curl install');
1364
                    curl_close($curl);
1365
                    return false;
1366
                }
1367
                break;
1368
            case 'h2':
1369
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
1370
                break;
1371
        }
1372
 
1373
        if ($opts['username'] && $opts['password']) {
1374
            curl_setopt($curl, CURLOPT_USERPWD, $opts['username'] . ':' . $opts['password']);
1375
            if (defined('CURLOPT_HTTPAUTH')) {
1376
                curl_setopt($curl, CURLOPT_HTTPAUTH, $opts['authtype']);
1377
            } elseif ($opts['authtype'] != 1) {
1378
                $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported by the current PHP/curl install');
1379
                curl_close($curl);
1380
                return false;
1381
            }
1382
        }
1383
 
1384
        // note: h2c is http2 without the https. No need to have it in this IF
1385
        if ($method == 'https' || $method == 'h2') {
1386
            // set cert file
1387
            if ($opts['cert']) {
1388
                curl_setopt($curl, CURLOPT_SSLCERT, $opts['cert']);
1389
            }
1390
            // set cert password
1391
            if ($opts['certpass']) {
1392
                curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $opts['certpass']);
1393
            }
1394
            // whether to verify remote host's cert
1395
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $opts['verifypeer']);
1396
            // set ca certificates file/dir
1397
            if ($opts['cacert']) {
1398
                curl_setopt($curl, CURLOPT_CAINFO, $opts['cacert']);
1399
            }
1400
            if ($opts['cacertdir']) {
1401
                curl_setopt($curl, CURLOPT_CAPATH, $opts['cacertdir']);
1402
            }
1403
            // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1404
            if ($opts['key']) {
1405
                curl_setopt($curl, CURLOPT_SSLKEY, $opts['key']);
1406
            }
1407
            // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1408
            if ($opts['keypass']) {
1409
                curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $opts['keypass']);
1410
            }
1411
            // whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that
1412
            // it matches the hostname used
1413
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $opts['verifyhost']);
1414
            // allow usage of different SSL versions
1415
            curl_setopt($curl, CURLOPT_SSLVERSION, $opts['sslversion']);
1416
        }
1417
 
1418
        // proxy info
1419
        if ($opts['proxy']) {
1420
            if ($opts['proxyport'] == 0) {
1421
                $opts['proxyport'] = 8080; // NB: even for HTTPS, local connection is on port 8080
1422
            }
1423
            curl_setopt($curl, CURLOPT_PROXY, $opts['proxy'] . ':' . $opts['proxyport']);
1424
            if ($opts['proxy_user']) {
1425
                curl_setopt($curl, CURLOPT_PROXYUSERPWD, $opts['proxy_user'] . ':' . $opts['proxy_pass']);
1426
                if (defined('CURLOPT_PROXYAUTH')) {
1427
                    curl_setopt($curl, CURLOPT_PROXYAUTH, $opts['proxy_authtype']);
1428
                } elseif ($opts['proxy_authtype'] != 1) {
1429
                    $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported by the current PHP/curl install');
1430
                    curl_close($curl);
1431
                    return false;
1432
                }
1433
            }
1434
        }
1435
 
1436
        // NB: should we build cookie http headers by hand rather than let CURL do it?
1437
        // NB: the following code does not honour 'expires', 'path' and 'domain' cookie attributes set to client obj by the user...
1438
        if (count($opts['cookies'])) {
1439
            $cookieHeader = '';
1440
            foreach ($opts['cookies'] as $name => $cookie) {
1441
                $cookieHeader .= $name . '=' . $cookie['value'] . '; ';
1442
            }
1443
            curl_setopt($curl, CURLOPT_COOKIE, substr($cookieHeader, 0, -2));
1444
        }
1445
 
1446
        foreach ($opts['extracurlopts'] as $opt => $val) {
1447
            curl_setopt($curl, $opt, $val);
1448
        }
1449
 
1450
        if ($opts['debug'] > 1) {
1451
            $this->getLogger()->debug("---SENDING---\n$payload\n---END---");
1452
        }
1453
 
1454
        return $curl;
1455
    }
1456
 
1457
    /**
1458
     * Send an array of requests and return an array of responses.
1459
     *
1460
     * Unless $this->no_multicall has been set to true, it will try first to use one single xml-rpc call to server method
1461
     * system.multicall, and revert to sending many successive calls in case of failure.
1462
     * This failure is also stored in $this->no_multicall for subsequent calls.
1463
     * Unfortunately, there is no server error code universally used to denote the fact that multicall is unsupported,
1464
     * so there is no way to reliably distinguish between that and a temporary failure.
1465
     * If you are sure that server supports multicall and do not want to fallback to using many single calls, set the
1466
     * 2np parameter to FALSE.
1467
     *
1468
     * NB: trying to shoehorn extra functionality into existing syntax has resulted
1469
     * in pretty much convoluted code...
1470
     *
1471
     * @param Request[] $reqs an array of Request objects
1472
     * @param bool $noFallback When true, upon receiving an error during multicall, multiple single calls will not be
1473
     *                         attempted.
1474
     *                         Deprecated alternative, was: int - "connection timeout (in seconds). See the details in the
1475
     *                         docs for the send() method". Please use setOption instead to set a timeout
1476
     * @param string $method deprecated. Was: "the http protocol variant to be used. See the details in the docs for the send() method."
1477
     *                       Please use the constructor to set an http protocol variant.
1478
     * @param boolean $fallback deprecated. Was: "w"hen true, upon receiving an error during multicall, multiple single
1479
     *                          calls will be attempted"
1480
     * @return Response[]
1481
     */
1482
    public function multicall($reqs, $timeout = 0, $method = '', $fallback = true)
1483
    {
1484
        // BC
1485
        if (is_bool($timeout) && $fallback === true) {
1486
            $fallback = !$timeout;
1487
            $timeout = 0;
1488
        }
1489
 
1490
        if ($method == '') {
1491
            $method = $this->method;
1492
        }
1493
 
1494
        if (!$this->no_multicall) {
1495
            $results = $this->_try_multicall($reqs, $timeout, $method);
1496
            /// @todo how to handle the case of $this->return_type = xml?
1497
            if (is_array($results)) {
1498
                // System.multicall succeeded
1499
                return $results;
1500
            } else {
1501
                // either system.multicall is unsupported by server, or the call failed for some other reason.
1502
                // Feature creep: is there a way to tell apart unsupported multicall from other faults?
1503
                if ($fallback) {
1504
                    // Don't try it next time...
1505
                    $this->no_multicall = true;
1506
                } else {
1507
                    $result = $results;
1508
                }
1509
            }
1510
        } else {
1511
            // override fallback, in case careless user tries to do two
1512
            // opposite things at the same time
1513
            $fallback = true;
1514
        }
1515
 
1516
        $results = array();
1517
        if ($fallback) {
1518
            // system.multicall is (probably) unsupported by server: emulate multicall via multiple requests
1519
            /// @todo use curl multi_ functions to make this quicker (see the implementation in the parallel.php demo)
1520
            foreach ($reqs as $req) {
1521
                $results[] = $this->send($req, $timeout, $method);
1522
            }
1523
        } else {
1524
            // user does NOT want to fallback on many single calls: since we should always return an array of responses,
1525
            // we return an array with the same error repeated n times
1526
            foreach ($reqs as $req) {
1527
                $results[] = $result;
1528
            }
1529
        }
1530
 
1531
        return $results;
1532
    }
1533
 
1534
    /**
1535
     * Attempt to boxcar $reqs via system.multicall.
1536
     *
1537
     * @param Request[] $reqs
1538
     * @param int $timeout
1539
     * @param string $method
1540
     * @return Response[]|Response a single Response when the call returned a fault / does not conform to what we expect
1541
     *                             from a multicall response
1542
     */
1543
    private function _try_multicall($reqs, $timeout, $method)
1544
    {
1545
        // Construct multicall request
1546
        $calls = array();
1547
        foreach ($reqs as $req) {
1548
            $call['methodName'] = new Value($req->method(), 'string');
1549
            $numParams = $req->getNumParams();
1550
            $params = array();
1551
            for ($i = 0; $i < $numParams; $i++) {
1552
                $params[$i] = $req->getParam($i);
1553
            }
1554
            $call['params'] = new Value($params, 'array');
1555
            $calls[] = new Value($call, 'struct');
1556
        }
1557
        $multiCall = new static::$requestClass('system.multicall');
1558
        $multiCall->addParam(new Value($calls, 'array'));
1559
 
1560
        // Attempt RPC call
1561
        $result = $this->send($multiCall, $timeout, $method);
1562
 
1563
        if ($result->faultCode() != 0) {
1564
            // call to system.multicall failed
1565
            return $result;
1566
        }
1567
 
1568
        // Unpack responses.
1569
        $rets = $result->value();
1570
        $response = array();
1571
 
1572
        if ($this->return_type == 'xml') {
1573
            for ($i = 0; $i < count($reqs); $i++) {
1574
                $response[] = new static::$responseClass($rets, 0, '', 'xml', $result->httpResponse());
1575
            }
1576
 
1577
        } elseif ($this->return_type == 'phpvals') {
1578
            if (!is_array($rets)) {
1579
                // bad return type from system.multicall
1580
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1581
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': not an array', 'phpvals', $result->httpResponse());
1582
            }
1583
            $numRets = count($rets);
1584
            if ($numRets != count($reqs)) {
1585
                // wrong number of return values.
1586
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1587
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': incorrect number of responses', 'phpvals',
1588
                    $result->httpResponse());
1589
            }
1590
 
1591
            for ($i = 0; $i < $numRets; $i++) {
1592
                $val = $rets[$i];
1593
                if (!is_array($val)) {
1594
                    return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1595
                        PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array or struct",
1596
                        'phpvals', $result->httpResponse());
1597
                }
1598
                switch (count($val)) {
1599
                    case 1:
1600
                        if (!isset($val[0])) {
1601
                            // Bad value
1602
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1603
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has no value",
1604
                                'phpvals', $result->httpResponse());
1605
                        }
1606
                        // Normal return value
1607
                        $response[$i] = new static::$responseClass($val[0], 0, '', 'phpvals', $result->httpResponse());
1608
                        break;
1609
                    case 2:
1610
                        /// @todo remove usage of @: it is apparently quite slow
1611
                        $code = @$val['faultCode'];
1612
                        if (!is_int($code)) {
1613
                            /// @todo should we check that it is != 0?
1614
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1615
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1616
                                'phpvals', $result->httpResponse());
1617
                        }
1618
                        $str = @$val['faultString'];
1619
                        if (!is_string($str)) {
1620
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1621
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no FaultString",
1622
                                'phpvals', $result->httpResponse());
1623
                        }
1624
                        $response[$i] = new static::$responseClass(0, $code, $str, 'phpvals', $result->httpResponse());
1625
                        break;
1626
                    default:
1627
                        return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1628
                            PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1629
                            'phpvals', $result->httpResponse());
1630
                }
1631
            }
1632
 
1633
        } else {
1634
            // return type == 'xmlrpcvals'
1635
            if ($rets->kindOf() != 'array') {
1636
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1637
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array", 'xmlrpcvals',
1638
                    $result->httpResponse());
1639
            }
1640
            $numRets = $rets->count();
1641
            if ($numRets != count($reqs)) {
1642
                // wrong number of return values.
1643
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1644
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': incorrect number of responses', 'xmlrpcvals',
1645
                    $result->httpResponse());
1646
            }
1647
 
1648
            foreach ($rets as $i => $val) {
1649
                switch ($val->kindOf()) {
1650
                    case 'array':
1651
                        if ($val->count() != 1) {
1652
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1653
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1654
                                'phpvals', $result->httpResponse());
1655
                        }
1656
                        // Normal return value
1657
                        $response[] = new static::$responseClass($val[0], 0, '', 'xmlrpcvals', $result->httpResponse());
1658
                        break;
1659
                    case 'struct':
1660
                        if ($val->count() != 2) {
1661
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1662
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1663
                                'phpvals', $result->httpResponse());
1664
                        }
1665
                        /** @var Value $code */
1666
                        $code = $val['faultCode'];
1667
                        if ($code->kindOf() != 'scalar' || $code->scalarTyp() != 'int') {
1668
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1669
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1670
                                'xmlrpcvals', $result->httpResponse());
1671
                        }
1672
                        /** @var Value $str */
1673
                        $str = $val['faultString'];
1674
                        if ($str->kindOf() != 'scalar' || $str->scalarTyp() != 'string') {
1675
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1676
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1677
                                'xmlrpcvals', $result->httpResponse());
1678
                        }
1679
                        $response[] = new static::$responseClass(0, $code->scalarVal(), $str->scalarVal(), 'xmlrpcvals', $result->httpResponse());
1680
                        break;
1681
                    default:
1682
                        return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1683
                            PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array or struct",
1684
                            'xmlrpcvals', $result->httpResponse());
1685
                }
1686
            }
1687
        }
1688
 
1689
        return $response;
1690
    }
1691
 
1692
    // *** BC layer ***
1693
 
1694
    /**
1695
     * @deprecated
1696
     *
1697
     * @param Request $req
1698
     * @param string $server
1699
     * @param int $port
1700
     * @param int $timeout
1701
     * @param string $username
1702
     * @param string $password
1703
     * @param int $authType
1704
     * @param string $proxyHost
1705
     * @param int $proxyPort
1706
     * @param string $proxyUsername
1707
     * @param string $proxyPassword
1708
     * @param int $proxyAuthType
1709
     * @param string $method
1710
     * @return Response
1711
     */
1712
    protected function sendPayloadHTTP10($req, $server, $port, $timeout = 0, $username = '', $password = '',
1713
        $authType = 1, $proxyHost = '', $proxyPort = 0, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1,
1714
        $method = 'http')
1715
    {
1716
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
1717
 
1718
        return $this->sendPayloadSocket($req, $server, $port, $timeout, $username, $password, $authType, null, null,
1719
            null, null, $proxyHost, $proxyPort, $proxyUsername, $proxyPassword, $proxyAuthType, $method);
1720
    }
1721
 
1722
    /**
1723
     * @deprecated
1724
     *
1725
     * @param Request $req
1726
     * @param string $server
1727
     * @param int $port
1728
     * @param int $timeout
1729
     * @param string $username
1730
     * @param string $password
1731
     * @param int $authType
1732
     * @param string $cert
1733
     * @param string $certPass
1734
     * @param string $caCert
1735
     * @param string $caCertDir
1736
     * @param string $proxyHost
1737
     * @param int $proxyPort
1738
     * @param string $proxyUsername
1739
     * @param string $proxyPassword
1740
     * @param int $proxyAuthType
1741
     * @param bool $keepAlive
1742
     * @param string $key
1743
     * @param string $keyPass
1744
     * @param int $sslVersion
1745
     * @return Response
1746
     */
1747
    protected function sendPayloadHTTPS($req, $server, $port, $timeout = 0, $username = '', $password = '',
1748
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
1749
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $keepAlive = false, $key = '', $keyPass = '',
1750
        $sslVersion = 0)
1751
    {
1752
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
1753
 
1754
        return $this->sendPayloadCURL($req, $server, $port, $timeout, $username,
1755
            $password, $authType, $cert, $certPass, $caCert, $caCertDir, $proxyHost, $proxyPort,
1756
            $proxyUsername, $proxyPassword, $proxyAuthType, 'https', $keepAlive, $key, $keyPass, $sslVersion);
1757
    }
1758
 
1759
    /**
1760
     * @deprecated
1761
     *
1762
     * @param Request $req
1763
     * @param string $server
1764
     * @param int $port
1765
     * @param int $timeout
1766
     * @param string $username
1767
     * @param string $password
1768
     * @param int $authType only value supported is 1
1769
     * @param string $cert
1770
     * @param string $certPass
1771
     * @param string $caCert
1772
     * @param string $caCertDir
1773
     * @param string $proxyHost
1774
     * @param int $proxyPort
1775
     * @param string $proxyUsername
1776
     * @param string $proxyPassword
1777
     * @param int $proxyAuthType only value supported is 1
1778
     * @param string $method 'http' (synonym for 'http10'), 'http10' or 'https'
1779
     * @param string $key
1780
     * @param string $keyPass @todo not implemented yet.
1781
     * @param int $sslVersion @todo not implemented yet. See http://php.net/manual/en/migration56.openssl.php
1782
     * @return Response
1783
     */
1784
    protected function sendPayloadSocket($req, $server, $port, $timeout = 0, $username = '', $password = '',
1785
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
1786
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'http', $key = '', $keyPass = '',
1787
        $sslVersion = 0)
1788
    {
1789
        $this->logDeprecationUnlessCalledBy('send');
1790
 
1791
        return $this->sendViaSocket($req, $method, $server, $port, $this->path, array(
1792
            'accepted_charset_encodings' => $this->accepted_charset_encodings,
1793
            'accepted_compression' => $this->accepted_compression,
1794
            'authtype' => $authType,
1795
            'cacert' => $caCert,
1796
            'cacertdir' => $caCertDir,
1797
            'cert' => $cert,
1798
            'certpass' => $certPass,
1799
            'cookies' => $this->cookies,
1800
            'debug' => $this->debug,
1801
            'extracurlopts' => $this->extracurlopts,
1802
            'extrasockopts' => $this->extrasockopts,
1803
            'keepalive' => $this->keepalive,
1804
            'key' => $key,
1805
            'keypass' => $keyPass,
1806
            'no_multicall' => $this->no_multicall,
1807
            'password' => $password,
1808
            'proxy' => $proxyHost,
1809
            'proxy_authtype' => $proxyAuthType,
1810
            'proxy_pass' => $proxyPassword,
1811
            'proxyport' => $proxyPort,
1812
            'proxy_user' => $proxyUsername,
1813
            'request_charset_encoding' => $this->request_charset_encoding,
1814
            'request_compression' => $this->request_compression,
1815
            'return_type' => $this->return_type,
1816
            'sslversion' => $sslVersion,
1817
            'timeout' => $timeout,
1818
            'username' => $username,
1819
            'user_agent' => $this->user_agent,
1820
            'use_curl' => $this->use_curl,
1821
            'verifyhost' => $this->verifyhost,
1822
            'verifypeer' => $this->verifypeer,
1823
        ));
1824
    }
1825
 
1826
    /**
1827
     * @deprecated
1828
     *
1829
     * @param Request $req
1830
     * @param string $server
1831
     * @param int $port
1832
     * @param int $timeout
1833
     * @param string $username
1834
     * @param string $password
1835
     * @param int $authType
1836
     * @param string $cert
1837
     * @param string $certPass
1838
     * @param string $caCert
1839
     * @param string $caCertDir
1840
     * @param string $proxyHost
1841
     * @param int $proxyPort
1842
     * @param string $proxyUsername
1843
     * @param string $proxyPassword
1844
     * @param int $proxyAuthType
1845
     * @param string $method 'http' (let curl decide), 'http10', 'http11', 'https', 'h2c' or 'h2'
1846
     * @param bool $keepAlive
1847
     * @param string $key
1848
     * @param string $keyPass
1849
     * @param int $sslVersion
1850
     * @return Response
1851
     */
1852
    protected function sendPayloadCURL($req, $server, $port, $timeout = 0, $username = '', $password = '',
1853
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
1854
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'https', $keepAlive = false, $key = '',
1855
        $keyPass = '', $sslVersion = 0)
1856
    {
1857
        $this->logDeprecationUnlessCalledBy('send');
1858
 
1859
        return $this->sendViaCURL($req, $method, $server, $port, $this->path, array(
1860
            'accepted_charset_encodings' => $this->accepted_charset_encodings,
1861
            'accepted_compression' => $this->accepted_compression,
1862
            'authtype' => $authType,
1863
            'cacert' => $caCert,
1864
            'cacertdir' => $caCertDir,
1865
            'cert' => $cert,
1866
            'certpass' => $certPass,
1867
            'cookies' => $this->cookies,
1868
            'debug' => $this->debug,
1869
            'extracurlopts' => $this->extracurlopts,
1870
            'extrasockopts' => $this->extrasockopts,
1871
            'keepalive' => $keepAlive,
1872
            'key' => $key,
1873
            'keypass' => $keyPass,
1874
            'no_multicall' => $this->no_multicall,
1875
            'password' => $password,
1876
            'proxy' => $proxyHost,
1877
            'proxy_authtype' => $proxyAuthType,
1878
            'proxy_pass' => $proxyPassword,
1879
            'proxyport' => $proxyPort,
1880
            'proxy_user' => $proxyUsername,
1881
            'request_charset_encoding' => $this->request_charset_encoding,
1882
            'request_compression' => $this->request_compression,
1883
            'return_type' => $this->return_type,
1884
            'sslversion' => $sslVersion,
1885
            'timeout' => $timeout,
1886
            'username' => $username,
1887
            'user_agent' => $this->user_agent,
1888
            'use_curl' => $this->use_curl,
1889
            'verifyhost' => $this->verifyhost,
1890
            'verifypeer' => $this->verifypeer,
1891
        ));
1892
    }
1893
 
1894
    /**
1895
     * @deprecated
1896
     *
1897
     * @param $req
1898
     * @param $server
1899
     * @param $port
1900
     * @param $timeout
1901
     * @param $username
1902
     * @param $password
1903
     * @param $authType
1904
     * @param $cert
1905
     * @param $certPass
1906
     * @param $caCert
1907
     * @param $caCertDir
1908
     * @param $proxyHost
1909
     * @param $proxyPort
1910
     * @param $proxyUsername
1911
     * @param $proxyPassword
1912
     * @param $proxyAuthType
1913
     * @param $method
1914
     * @param $keepAlive
1915
     * @param $key
1916
     * @param $keyPass
1917
     * @param $sslVersion
1918
     * @return false|\CurlHandle|resource
1919
     */
1920
    protected function prepareCurlHandle($req, $server, $port, $timeout = 0, $username = '', $password = '',
1921
         $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
1922
         $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'https', $keepAlive = false, $key = '',
1923
         $keyPass = '', $sslVersion = 0)
1924
    {
1925
        $this->logDeprecationUnlessCalledBy('sendViaCURL');
1926
 
1927
        return $this->createCURLHandle($req, $method, $server, $port, $this->path, array(
1928
            'accepted_charset_encodings' => $this->accepted_charset_encodings,
1929
            'accepted_compression' => $this->accepted_compression,
1930
            'authtype' => $authType,
1931
            'cacert' => $caCert,
1932
            'cacertdir' => $caCertDir,
1933
            'cert' => $cert,
1934
            'certpass' => $certPass,
1935
            'cookies' => $this->cookies,
1936
            'debug' => $this->debug,
1937
            'extracurlopts' => $this->extracurlopts,
1938
            'keepalive' => $keepAlive,
1939
            'key' => $key,
1940
            'keypass' => $keyPass,
1941
            'no_multicall' => $this->no_multicall,
1942
            'password' => $password,
1943
            'proxy' => $proxyHost,
1944
            'proxy_authtype' => $proxyAuthType,
1945
            'proxy_pass' => $proxyPassword,
1946
            'proxyport' => $proxyPort,
1947
            'proxy_user' => $proxyUsername,
1948
            'request_charset_encoding' => $this->request_charset_encoding,
1949
            'request_compression' => $this->request_compression,
1950
            'return_type' => $this->return_type,
1951
            'sslversion' => $sslVersion,
1952
            'timeout' => $timeout,
1953
            'username' => $username,
1954
            'user_agent' => $this->user_agent,
1955
            'use_curl' => $this->use_curl,
1956
            'verifyhost' => $this->verifyhost,
1957
            'verifypeer' => $this->verifypeer,
1958
        ));
1959
    }
1960
 
1961
    // we have to make this return by ref in order to allow calls such as `$resp->_cookies['name'] = ['value' => 'something'];`
1962
    public function &__get($name)
1963
    {
1964
        if (in_array($name, static::$options)) {
1965
            $this->logDeprecation('Getting property Client::' . $name . ' is deprecated');
1966
            return $this->$name;
1967
        }
1968
 
1969
        switch ($name) {
1970
            case 'errno':
1971
            case 'errstr':
1972
            case 'method':
1973
            case 'server':
1974
            case 'port':
1975
            case 'path':
1976
                $this->logDeprecation('Getting property Client::' . $name . ' is deprecated');
1977
                return $this->$name;
1978
            default:
1979
                /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout...
1980
                $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
1981
                trigger_error('Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
1982
                $result = null;
1983
                return $result;
1984
        }
1985
    }
1986
 
1987
    public function __set($name, $value)
1988
    {
1989
        if (in_array($name, static::$options)) {
1990
            $this->logDeprecation('Setting property Client::' . $name . ' is deprecated');
1991
            $this->$name = $value;
1992
            return;
1993
        }
1994
 
1995
        switch ($name) {
1996
            case 'errno':
1997
            case 'errstr':
1998
            case 'method':
1999
            case 'server':
2000
            case 'port':
2001
            case 'path':
2002
                $this->logDeprecation('Setting property Client::' . $name . ' is deprecated');
2003
                $this->$name = $value;
2004
                return;
2005
            default:
2006
                /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout...
2007
                $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
2008
                trigger_error('Undefined property via __set(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
2009
        }
2010
    }
2011
 
2012
    public function __isset($name)
2013
    {
2014
        if (in_array($name, static::$options)) {
2015
            $this->logDeprecation('Checking property Client::' . $name . ' is deprecated');
2016
            return isset($this->$name);
2017
        }
2018
 
2019
        switch ($name) {
2020
            case 'errno':
2021
            case 'errstr':
2022
            case 'method':
2023
            case 'server':
2024
            case 'port':
2025
            case 'path':
2026
                $this->logDeprecation('Checking property Client::' . $name . ' is deprecated');
2027
                return isset($this->$name);
2028
            default:
2029
                return false;
2030
        }
2031
    }
2032
 
2033
    public function __unset($name)
2034
    {
2035
        if (in_array($name, static::$options)) {
2036
            $this->logDeprecation('Unsetting property Client::' . $name . ' is deprecated');
2037
            unset($this->$name);
2038
            return;
2039
        }
2040
 
2041
        switch ($name) {
2042
            case 'errno':
2043
            case 'errstr':
2044
            case 'method':
2045
            case 'server':
2046
            case 'port':
2047
            case 'path':
2048
                $this->logDeprecation('Unsetting property Client::' . $name . ' is deprecated');
2049
                unset($this->$name);
2050
                return;
2051
            default:
2052
                /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout...
2053
                $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
2054
                trigger_error('Undefined property via __unset(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
2055
        }
2056
    }
2057
}