Issues (319)

src/Client.php (13 issues)

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
    const OPT_EXTRA_HEADERS = 'extra_headers';
61
62
    /** @var string */
63
    protected static $requestClass = '\\PhpXmlRpc\\Request';
64
    /** @var string */
65
    protected static $responseClass = '\\PhpXmlRpc\\Response';
66
67
    /**
68
     * @var int
69
     * @deprecated will be removed in the future
70
     */
71
    protected $errno;
72
    /**
73
     * @var string
74
     * @deprecated will be removed in the future
75
     */
76
    protected $errstr;
77
78
    /// @todo: do all the ones below need to be public?
79
80
    /**
81
     * @var string
82
     */
83
    protected $method = 'http';
84
    /**
85
     * @var string
86
     */
87
    protected $server;
88
    /**
89
     * @var int
90
     */
91
    protected $port = 0;
92
    /**
93
     * @var string
94
     */
95
    protected $path;
96
97
    /**
98
     * @var int
99
     */
100
    protected $debug = 0;
101
    /**
102
     * @var string
103
     */
104
    protected $username = '';
105
    /**
106
     * @var string
107
     */
108
    protected $password = '';
109
    /**
110
     * @var int
111
     */
112
    protected $authtype = 1;
113
    /**
114
     * @var string
115
     */
116
    protected $cert = '';
117
    /**
118
     * @var string
119
     */
120
    protected $certpass = '';
121
    /**
122 2
     * @var string
123
     */
124 2
    protected $cacert = '';
125 2
    /**
126
     * @var string
127 2
     */
128
    protected $cacertdir = '';
129
    /**
130
     * @var string
131
     */
132
    protected $key = '';
133
    /**
134
     * @var string
135
     */
136
    protected $keypass = '';
137
    /**
138
     * @var bool
139
     */
140
    protected $verifypeer = true;
141
    /**
142
     * @var int
143
     */
144
    protected $verifyhost = 2;
145
    /**
146
     * @var int Corresponds to CURL_SSLVERSION_DEFAULT. Other CURL_SSLVERSION_ values are supported when in curl mode,
147
     *          and in socket mode different values from 0 to 7, matching the corresponding curl value. Old php versions
148
     *          do not support all values, php 5.4 and 5.5 do not support any in fact
149
     */
150
    protected $sslversion = 0;
151
    /**
152 718
     * @var string
153
     */
154
    protected $proxy = '';
155 718
    /**
156 7
     * @var int
157 7
     */
158 7
    protected $proxyport = 0;
159 7
    /**
160
     * @var string
161
     */
162 7
    protected $proxy_user = '';
163
    /**
164
     * @var string
165 7
     */
166
    protected $proxy_pass = '';
167
    /**
168 7
     * @var int
169 7
     */
170
    protected $proxy_authtype = 1;
171 7
    /**
172
     * @var array
173
     */
174 7
    protected $cookies = array();
175
    /**
176
     * @var array
177
     */
178 718
    protected $extrasockopts = array();
179
    /**
180
     * @var array
181 718
     */
182
    protected $extracurlopts = array();
183 718
    /**
184 718
     * @var int
185 3
     */
186
    protected $timeout = 0;
187 718
    /**
188 7
     * @var int
189
     */
190
    protected $use_curl = self::USE_CURL_AUTO;
191
    /**
192 718
     * @var bool
193
     *
194 711
     * This determines whether the multicall() method will try to take advantage of the system.multicall xml-rpc method
195
     * to dispatch to the server an array of requests in a single http roundtrip or simply execute many consecutive http
196
     * calls. Defaults to FALSE, but it will be enabled automatically on the first failure of execution of
197 718
     * system.multicall.
198
     */
199
    protected $no_multicall = false;
200
    /**
201 718
     * @var array
202
     *
203
     * List of http compression methods accepted by the client for responses.
204 718
     * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib.
205
     *
206
     * NNB: you can set it to any non-empty array for HTTP11 and HTTPS, since in those cases it will be up to CURL to
207
     * decide the compression methods it supports. You might check for the presence of 'zlib' in the output of
208
     * curl_version() to determine whether compression is supported or not
209
     */
210
    protected $accepted_compression = array();
211
    /**
212
     * @var string|null
213
     *
214
     * Name of compression scheme to be used for sending requests.
215
     * Either null, 'gzip' or 'deflate'.
216
     */
217 718
    protected $request_compression = '';
218 718
    /**
219
     * @var bool
220
     *
221
     * Whether to use persistent connections for http 1.1 and https. Value set at constructor time.
222
     */
223
    protected $keepalive = false;
224
    /**
225
     * @var string[]
226
     *
227
     * Charset encodings that can be decoded without problems by the client. Value set at constructor time
228
     */
229
    protected $accepted_charset_encodings = array();
230
    /**
231
     * @var string
232
     *
233 714
     * The charset encoding that will be used for serializing request sent by the client.
234
     * It defaults to NULL, which means using US-ASCII and encoding all characters outside the ASCII printable range
235 714
     * using their xml character entity representation (this has the benefit that line end characters will not be mangled
236 714
     * in the transfer, a CR-LF will be preserved as well as a singe LF).
237
     * Valid values are 'US-ASCII', 'UTF-8' and 'ISO-8859-1'.
238
     * For the fastest mode of operation, set your both your app internal encoding and this to UTF-8.
239
     */
240
    protected $request_charset_encoding = '';
241
    /**
242
     * @var string
243
     *
244
     * Decides the content of Response objects returned by calls to send() and multicall().
245
     * Valid values are 'xmlrpcvals', 'phpvals' or 'xml'.
246
     *
247
     * Determines whether the value returned inside a Response object as results of calls to the send() and multicall()
248
     * methods will be a Value object, a plain php value or a raw xml string.
249
     * Allowed values are 'xmlrpcvals' (the default), 'phpvals' and 'xml'.
250
     * To allow the user to differentiate between a correct and a faulty response, fault responses will be returned as
251
     * Response objects in any case.
252 66
     * Note that the 'phpvals' setting will yield faster execution times, but some of the information from the original
253
     * response will be lost. It will be e.g. impossible to tell whether a particular php string value was sent by the
254 66
     * server as an xml-rpc string or base64 value.
255 66
     */
256 66
    protected $return_type = XMLParser::RETURN_XMLRPCVALS;
257 66
    /**
258
     * @var string
259
     *
260
     * Sent to servers in http headers. Value set at constructor time.
261
     */
262
    protected $user_agent;
263
264
    /**
265
     * Additional headers to be included in the requests.
266
     *
267
     * @var string[]
268
     */
269
    protected $extra_headers = array();
270
271
    /**
272
     * CURL handle: used for keep-alive
273
     * @internal
274
     */
275
    public $xmlrpc_curl_handle = null;
276
277
    /**
278
     * @var array
279
     */
280
    protected static $options = array(
281
        self::OPT_ACCEPTED_CHARSET_ENCODINGS,
282
        self::OPT_ACCEPTED_COMPRESSION,
283
        self::OPT_AUTH_TYPE,
284
        self::OPT_CA_CERT,
285
        self::OPT_CA_CERT_DIR,
286
        self::OPT_CERT,
287
        self::OPT_CERT_PASS,
288
        self::OPT_COOKIES,
289
        self::OPT_DEBUG,
290
        self::OPT_EXTRA_CURL_OPTS,
291
        self::OPT_EXTRA_SOCKET_OPTS,
292
        self::OPT_KEEPALIVE,
293
        self::OPT_KEY,
294
        self::OPT_KEY_PASS,
295
        self::OPT_NO_MULTICALL,
296
        self::OPT_PASSWORD,
297
        self::OPT_PROXY,
298
        self::OPT_PROXY_AUTH_TYPE,
299
        self::OPT_PROXY_PASS,
300
        self::OPT_PROXY_USER,
301
        self::OPT_PROXY_PORT,
302
        self::OPT_REQUEST_CHARSET_ENCODING,
303
        self::OPT_REQUEST_COMPRESSION,
304
        self::OPT_RETURN_TYPE,
305
        self::OPT_SSL_VERSION,
306
        self::OPT_TIMEOUT,
307
        self::OPT_USE_CURL,
308
        self::OPT_USER_AGENT,
309
        self::OPT_USERNAME,
310
        self::OPT_VERIFY_HOST,
311
        self::OPT_VERIFY_PEER,
312
        self::OPT_EXTRA_HEADERS,
313
    );
314
315
    /**
316 132
     * @param string $path either the PATH part of the xml-rpc server URL, or complete server URL (in which case you
317
     *                     should use an empty string for all other parameters)
318 132
     *                     e.g. /xmlrpc/server.php
319 132
     *                     e.g. http://phpxmlrpc.sourceforge.net/server.php
320
     *                     e.g. https://james:[email protected]:444/xmlrpcserver?agent=007
321
     *                     e.g. h2://fast-and-secure-services.org/endpoint
322
     * @param string $server the server name / ip address
323
     * @param integer $port the port the server is listening on, when omitted defaults to 80 or 443 depending on
324
     *                      protocol used
325
     * @param string $method the http protocol variant: defaults to 'http'; 'https', 'http11', 'h2' and 'h2c' can
326
     *                       be used if CURL is installed. The value set here can be overridden in any call to $this->send().
327
     *                       Use 'h2' to make the lib attempt to use http/2 over a secure connection, and 'h2c'
328 132
     *                       for http/2 without tls. Note that 'h2c' will not use the h2c 'upgrade' method, and be
329
     *                       thus incompatible with any server/proxy not supporting http/2. This is because POST
330 132
     *                       request are not compatible with h2c upgrade.
331 132
     */
332
    public function __construct($path, $server = '', $port = '', $method = '')
333
    {
334
        // allow user to specify all params in $path
335
        if ($server == '' && $port == '' && $method == '') {
336
            $parts = parse_url($path);
337
            $server = $parts['host'];
338 132
            $path = isset($parts['path']) ? $parts['path'] : '';
339
            if (isset($parts['query'])) {
340 132
                $path .= '?' . $parts['query'];
341 132
            }
342
            if (isset($parts['fragment'])) {
343
                $path .= '#' . $parts['fragment'];
344
            }
345
            if (isset($parts['port'])) {
346
                $port = $parts['port'];
347
            }
348
            if (isset($parts['scheme'])) {
349
                $method = $parts['scheme'];
350
            }
351
            if (isset($parts['user'])) {
352
                $this->username = $parts['user'];
353
            }
354
            if (isset($parts['pass'])) {
355 99
                $this->password = $parts['pass'];
356
            }
357 99
        }
358 99
        if ($path == '' || $path[0] != '/') {
359 99
            $this->path = '/' . $path;
360 99
        } else {
361 99
            $this->path = $path;
362 99
        }
363
        $this->server = $server;
364
        if ($port != '') {
365
            $this->port = $port;
366
        }
367
        if ($method != '') {
368
            $this->method = $method;
369
        }
370
371
        // if ZLIB is enabled, let the client by default accept compressed responses
372
        if (function_exists('gzinflate') || (
373
                function_exists('curl_version') && (($info = curl_version()) &&
374
                    ((is_string($info) && strpos($info, 'zlib') !== null) || isset($info['libz_version'])))
375
            )
376
        ) {
377
            $this->accepted_compression = array('gzip', 'deflate');
378
        }
379
380
        // keepalives: enabled by default
381
        $this->keepalive = true;
382
383
        // by default the xml parser can support these 3 charset encodings
384
        $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII');
385
386
        // NB: this is disabled to avoid making all the requests sent huge... mbstring supports more than 80 charsets!
387
        //$ch = $this->getCharsetEncoder();
388
        //$this->accepted_charset_encodings = $ch->knownCharsets();
389
390
        // initialize user_agent string
391
        $this->user_agent = PhpXmlRpc::$xmlrpcName . ' ' . PhpXmlRpc::$xmlrpcVersion;
392
    }
393
394
    /**
395
     * @param string $name see all the OPT_ constants
396
     * @param mixed $value
397
     * @return $this
398
     * @throws ValueErrorException on unsupported option
399
     */
400
    public function setOption($name, $value)
401
    {
402
        if (in_array($name, static::$options)) {
403
            $this->$name = $value;
404
            return $this;
405
        }
406
407
        throw new ValueErrorException("Unsupported option '$name'");
408
    }
409
410
    /**
411
     * @param string $name see all the OPT_ constants
412
     * @return mixed
413
     * @throws ValueErrorException on unsupported option
414
     */
415
    public function getOption($name)
416
    {
417
        if (in_array($name, static::$options)) {
418 580
            return $this->$name;
419
        }
420 580
421 580
        throw new ValueErrorException("Unsupported option '$name'");
422
    }
423
424
    /**
425
     * Returns the complete list of Client options, with their value.
426
     * @return array
427 580
     */
428
    public function getOptions()
429 580
    {
430
        $values = array();
431
        foreach (static::$options as $opt) {
432
            $values[$opt] = $this->getOption($opt);
433
        }
434
        return $values;
435
    }
436
437
    /**
438
     * @param array $options key: any valid option (see all the OPT_ constants)
439
     * @return $this
440
     * @throws ValueErrorException on unsupported option
441
     */
442
    public function setOptions($options)
443
    {
444
        foreach ($options as $name => $value) {
445
            $this->setOption($name, $value);
446 66
        }
447
448 66
        return $this;
449 66
    }
450
451
    /**
452
     * Enable/disable the echoing to screen of the xml-rpc responses received. The default is not to output anything.
453
     *
454
     * The debugging information at level 1 includes the raw data returned from the XML-RPC server it was querying
455
     * (including bot HTTP headers and the full XML payload), and the PHP value the client attempts to create to
456
     * represent the value returned by the server.
457
     * At level 2, the complete payload of the xml-rpc request is also printed, before being sent to the server.
458
     * At level -1, the Response objects returned by send() calls will not carry information about the http response's
459
     * cookies, headers and body, which might save some memory
460
     *
461
     * This option can be very useful when debugging servers as it allows you to see exactly what the client sends and
462
     * the server returns. Never leave it enabled for production!
463
     *
464
     * @param integer $level values -1, 0, 1 and 2 are supported
465
     * @return $this
466
     */
467
    public function setDebug($level)
468
    {
469
        $this->debug = $level;
470
        return $this;
471
    }
472
473
    /**
474
     * Sets the username and password for authorizing the client to the server.
475
     *
476
     * With the default (HTTP) transport, this information is used for HTTP Basic authorization.
477
     * Note that username and password can also be set using the class constructor.
478
     * With HTTP 1.1 and HTTPS transport, NTLM and Digest authentication protocols are also supported. To enable them use
479
     * the constants CURLAUTH_DIGEST and CURLAUTH_NTLM as values for the auth type parameter.
480
     *
481
     * @param string $user username
482
     * @param string $password password
483
     * @param integer $authType auth type. See curl_setopt man page for supported auth types. Defaults to CURLAUTH_BASIC
484
     *                          (basic auth). Note that auth types NTLM and Digest will only work if the Curl php
485
     *                          extension is enabled.
486
     * @return $this
487
     */
488
    public function setCredentials($user, $password, $authType = 1)
489
    {
490
        $this->username = $user;
491
        $this->password = $password;
492
        $this->authtype = $authType;
493
        return $this;
494
    }
495 697
496
    /**
497
     * Set the optional certificate and passphrase used in SSL-enabled communication with a remote server.
498
     *
499 697
     * Note: to retrieve information about the client certificate on the server side, you will need to look into the
500 117
     * environment variables which are set up by the webserver. Different webservers will typically set up different
501
     * variables.
502
     *
503 697
     * @param string $cert the name of a file containing a PEM formatted certificate
504
     * @param string $certPass the password required to use it
505 66
     * @return $this
506
     */
507 66
    public function setCertificate($cert, $certPass = '')
508 697
    {
509 28
        $this->cert = $cert;
510 28
        $this->certpass = $certPass;
511 28
        return $this;
512
    }
513
514
    /**
515 697
     * Add a CA certificate to verify server with in SSL-enabled communication when SetSSLVerifypeer has been set to TRUE.
516
     *
517
     * See the php manual page about CURLOPT_CAINFO for more details.
518
     *
519 697
     * @param string $caCert certificate file name (or dir holding certificates)
520 697
     * @param bool $isDir set to true to indicate cacert is a dir. defaults to false
521
     * @return $this
522 697
     */
523 322
    public function setCaCertificate($caCert, $isDir = false)
524 322
    {
525 322
        if ($isDir) {
526 322
            $this->cacertdir = $caCert;
527
        } else {
528 322
            $this->cacert = $caCert;
529 322
        }
530 322
        return $this;
531 322
    }
532 322
533 322
    /**
534 322
     * Set attributes for SSL communication: private SSL key.
535 322
     *
536 322
     * NB: does not work in older php/curl installs.
537 322
     * Thanks to Daniel Convissor.
538 322
     *
539 322
     * @param string $key The name of a file containing a private SSL key
540
     * @param string $keyPass The secret password needed to use the private SSL key
541 322
     * @return $this
542 322
     */
543 322
    public function setKey($key, $keyPass)
544 322
    {
545 322
        $this->key = $key;
546
        $this->keypass = $keyPass;
547
        return $this;
548
    }
549 375
550 375
    /**
551 375
     * Set attributes for SSL communication: verify the remote host's SSL certificate, and cause the connection to fail
552 375
     * if the cert verification fails.
553
     *
554 375
     * By default, verification is enabled.
555 375
     * To specify custom SSL certificates to validate the server with, use the setCaCertificate method.
556 375
     *
557 375
     * @param bool $i enable/disable verification of peer certificate
558 375
     * @return $this
559 375
     * @deprecated use setOption
560 375
     */
561 375
    public function setSSLVerifyPeer($i)
562 375
    {
563 375
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
564 375
565 375
        $this->verifypeer = $i;
566
        return $this;
567 375
    }
568 375
569 375
    /**
570
     * Set attributes for SSL communication: verify the remote host's SSL certificate's common name (CN).
571
     *
572
     * Note that support for value 1 has been removed in cURL 7.28.1
573 697
     *
574
     * @param int $i Set to 1 to only the existence of a CN, not that it matches
575
     * @return $this
576
     * @deprecated use setOption
577
     */
578
    public function setSSLVerifyHost($i)
579
    {
580
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
581
582
        $this->verifyhost = $i;
583
        return $this;
584
    }
585
586
    /**
587
     * Set attributes for SSL communication: SSL version to use. Best left at 0 (default value): let PHP decide.
588
     *
589
     * @param int $i use CURL_SSLVERSION_ constants. When in socket mode, use the same values: 2 (SSLv2) to 7 (TLSv1.3),
590
     *               0 for auto
591
     *               (note that old php versions do not support all TLS versions)
592
     * @return $this
593
     * @deprecated use setOption
594
     */
595
    public function setSSLVersion($i)
596
    {
597
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
598
599
        $this->sslversion = $i;
600
        return $this;
601
    }
602
603
    /**
604
     * Set proxy info.
605
     *
606
     * NB: CURL versions before 7.11.10 cannot use a proxy to communicate with https servers.
607
     *
608
     * @param string $proxyHost
609
     * @param string $proxyPort Defaults to 8080 for HTTP and 443 for HTTPS
610
     * @param string $proxyUsername Leave blank if proxy has public access
611
     * @param string $proxyPassword Leave blank if proxy has public access
612
     * @param int $proxyAuthType defaults to CURLAUTH_BASIC (Basic authentication protocol); set to constant CURLAUTH_NTLM
613
     *                           to use NTLM auth with proxy (has effect only when the client uses the HTTP 1.1 protocol)
614
     * @return $this
615
     */
616
    public function setProxy($proxyHost, $proxyPort, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1)
617
    {
618
        $this->proxy = $proxyHost;
619
        $this->proxyport = $proxyPort;
0 ignored issues
show
Documentation Bug introduced by
The property $proxyport was declared of type integer, but $proxyPort is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
620
        $this->proxy_user = $proxyUsername;
621
        $this->proxy_pass = $proxyPassword;
622
        $this->proxy_authtype = $proxyAuthType;
623
        return $this;
624
    }
625
626
    /**
627
     * Enables/disables reception of compressed xml-rpc responses.
628
     *
629
     * This requires the "zlib" extension to be enabled in your php install. If it is, by default xmlrpc_client
630
     * instances will enable reception of compressed content.
631
     * Note that enabling reception of compressed responses merely adds some standard http headers to xml-rpc requests.
632
     * It is up to the xml-rpc server to return compressed responses when receiving such requests.
633
     *
634
     * @param string $compMethod either 'gzip', 'deflate', 'any' or ''
635
     * @return $this
636
     */
637
    public function setAcceptedCompression($compMethod)
638
    {
639
        if ($compMethod == 'any') {
640
            $this->accepted_compression = array('gzip', 'deflate');
641
        } elseif ($compMethod == false) {
642
            $this->accepted_compression = array();
643
        } else {
644
            $this->accepted_compression = array($compMethod);
645
        }
646
        return $this;
647
    }
648
649
    /**
650
     * Enables/disables http compression of xml-rpc request.
651
     *
652
     * This requires the "zlib" extension to be enabled in your php install.
653
     * Take care when sending compressed requests: servers might not support them (and automatic fallback to
654
     * uncompressed requests is not yet implemented).
655
     *
656
     * @param string $compMethod either 'gzip', 'deflate' or ''
657
     * @return $this
658
     * @deprecated use setOption
659
     */
660
    public function setRequestCompression($compMethod)
661
    {
662
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
663
664 375
        $this->request_compression = $compMethod;
665
        return $this;
666
    }
667
668
    /**
669
     * Adds a cookie to list of cookies that will be sent to server with every further request (useful e.g. for keeping
670
     * session info outside the xml-rpc payload).
671 375
     *
672 373
     * NB: by default all cookies set via this method are sent to the server, regardless of path/domain/port. Taking
673
     * advantage of those values is left to the single developer.
674
     *
675
     * @param string $name nb: will not be escaped in the request's http headers. Take care not to use CTL chars or
676 375
     *                     separators!
677 346
     * @param string $value
678
     * @param string $path
679
     * @param string $domain
680 375
     * @param int $port do not use! Cookies are not separated by port
681
     * @return $this
682 375
     *
683 375
     * @todo check correctness of urlencoding cookie value (copied from php way of doing it, but php is generally sending
684 64
     *       response not requests. We do the opposite...)
685 32
     * @todo strip invalid chars from cookie name? As per RFC 6265, we should follow RFC 2616, Section 2.2
686 32
     * @todo drop/rename $port parameter. Cookies are not isolated by port!
687 32
     * @todo feature-creep allow storing 'expires', 'secure', 'httponly' and 'samesite' cookie attributes (we could do
688 32
     *       as php, and allow $path to be an array of attributes...)
689
     */
690
    public function setCookie($name, $value = '', $path = '', $domain = '', $port = null)
691 32
    {
692 32
        $this->cookies[$name]['value'] = rawurlencode($value);
693 32
        if ($path || $domain || $port) {
694 32
            $this->cookies[$name]['path'] = $path;
695
            $this->cookies[$name]['domain'] = $domain;
696
            $this->cookies[$name]['port'] = $port;
697
        }
698
        return $this;
699
    }
700 375
701 375
    /**
702 32
     * Directly set cURL options, for extra flexibility (when in cURL mode).
703 32
     *
704
     * It allows e.g. to bind client to a specific IP interface / address.
705
     *
706
     * @param array $options
707
     * @return $this
708 375
     * @deprecated use setOption
709 375
     */
710 73
    public function setCurlOptions($options)
711
    {
712
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
713 375
714 375
        $this->extracurlopts = $options;
715 32
        return $this;
716
    }
717
718 32
    /**
719 32
     * @param int $useCurlMode self::USE_CURL_ALWAYS, self::USE_CURL_AUTO or self::USE_CURL_NEVER
720 32
     * @return $this
721 32
     * @deprecated use setOption
722 32
     */
723
    public function setUseCurl($useCurlMode)
724
    {
725
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
726 32
727
        $this->use_curl = $useCurlMode;
728
        return $this;
729 343
    }
730 343
731 343
732 343
    /**
733
     * Set user-agent string that will be used by this client instance in http headers sent to the server.
734
     *
735
     * The default user agent string includes the name of this library and the version number.
736 375
     *
737 375
     * @param string $agentString
738 309
     * @return $this
739 309
     * @deprecated use setOption
740 309
     */
741
    public function setUserAgent($agentString)
742
    {
743
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
744
745
        $this->user_agent = $agentString;
746
        return $this;
747
    }
748
749
    /**
750
     * @param null|int $component allowed values: PHP_URL_SCHEME, PHP_URL_HOST, PHP_URL_PORT, PHP_URL_PATH
751
     * @return string|int Notes: the path component will include query string and fragment; NULL is a valid value for port
752
     *                    (in which case the default port for http/https will be used); the url scheme component will
753 309
     *                    reflect the `$method` used in the constructor, so it might not be http or https
754
     * @throws ValueErrorException on unsupported component
755
     */
756 309
    public function getUrl($component = null)
757
    {
758
        if (is_int($component) || ctype_digit($component)) {
759
            switch ($component) {
760 375
                case PHP_URL_SCHEME:
761 375
                    return $this->method;
762
                case PHP_URL_HOST:
763
                    return $this->server;
764
                case PHP_URL_PORT:
765
                    return $this->port;
766 375
                case  PHP_URL_PATH:
767 375
                    return $this->path;
768 375
                case '':
769 375
770 375
                default:
771 375
                    throw new ValueErrorException("Unsupported component '$component'");
772 375
            }
773 375
        }
774 375
775 375
        $url = $this->method . '://' . $this->server;
776 375
        if ($this->port == 0 || ($this->port == 80 && in_array($this->method, array('http', 'http10', 'http11', 'h2c'))) ||
777 375
            ($this->port == 443 && in_array($this->method, array('https', 'h2')))) {
778
            return $url . $this->path;
779 375
        } else {
780 2
            return $url . ':' . $this->port . $this->path;
781
        }
782
    }
783 375
784 375
    /**
785 32
     * Send an xml-rpc request to the server.
786
     *
787
     * @param Request|Request[]|string $req The Request object, or an array of requests for using multicall, or the
788
     *                                      complete xml representation of a request.
789
     *                                      When sending an array of Request objects, the client will try to make use of
790
     *                                      a single 'system.multicall' xml-rpc method call to forward to the server all
791 32
     *                                      the requests in a single HTTP round trip, unless $this->no_multicall has
792
     *                                      been previously set to TRUE (see the multicall method below), in which case
793
     *                                      many consecutive xml-rpc requests will be sent. The method will return an
794 32
     *                                      array of Response objects in both cases.
795
     *                                      The third variant allows to build by hand (or any other means) a complete
796
     *                                      xml-rpc request message, and send it to the server. $req should be a string
797 32
     *                                      containing the complete xml representation of the request. It is e.g. useful
798
     *                                      when, for maximal speed of execution, the request is serialized into a
799
     *                                      string using the native php xml-rpc functions (see http://www.php.net/xmlrpc)
800 32
     * @param integer $timeout deprecated. Connection timeout, in seconds, If unspecified, the timeout set with setOption
801 32
     *                         will be used. If that is 0, a platform specific timeout will apply.
802
     *                         This timeout value is passed to fsockopen(). It is also used for detecting server
803
     *                         timeouts during communication (i.e. if the server does not send anything to the client
804 375
     *                         for $timeout seconds, the connection will be closed). When in CURL mode, this is the
805
     *                         CURL timeout.
806 375
     *                         NB: in both CURL and Socket modes, some conditions might lead to the client not
807 64
     *                        respecting the given timeout. Eg. if the network is not connected
808
     * @param string $method deprecated. Use the same value in the constructor instead.
809 311
     *                       Valid values are 'http', 'http11', 'https', 'h2' and 'h2c'. If left empty,
810
     *                       the http protocol chosen during creation of the object will be used.
811
     *                       Use 'h2' to make the lib attempt to use http/2 over a secure connection, and 'h2c'
812 375
     *                       for http/2 without tls. Note that 'h2c' will not use the h2c 'upgrade' method, and be
813 375
     *                       thus incompatible with any server/proxy not supporting http/2. This is because POST
814
     *                       request are not compatible with h2c upgrade.
815 375
     * @return Response|Response[] Note that the client will always return a Response object, even if the call fails
816 375
     *
817 375
     * @todo allow throwing exceptions instead of returning responses in case of failed calls and/or Fault responses
818 374
     * @todo refactor: we now support many options besides connection timeout and http version to use. Why only privilege those?
819 367
     */
820
    public function send($req, $timeout = 0, $method = '')
821
    {
822 1
        if ($method !== '' || $timeout !== 0) {
823
            $this->logDeprecation("Using non-default values for arguments 'method' and 'timeout' when calling method " . __METHOD__ . ' is deprecated');
824
        }
825
826
        // if user does not specify http protocol, use native method of this client
827 1
        // (i.e. method set during call to constructor)
828 1
        if ($method == '') {
829
            $method = $this->method;
830 1
        }
831
832
        if ($timeout == 0) {
833 374
            $timeout = $this->timeout;
834
        }
835
836
        if (is_array($req)) {
837
            // $req is an array of Requests
838
            /// @todo switch to the new syntax for multicall
839
            return $this->multicall($req, $timeout, $method);
840
        } elseif (is_string($req)) {
841
            $n = new static::$requestClass('');
842
            /// @todo we should somehow allow the caller to declare a custom contenttype too, esp. for the charset declaration
843 374
            $n->setPayload($req);
844
            $req = $n;
845
        }
846
847 374
        // where req is a Request
848 374
        $req->setDebug($this->debug);
849 374
850
        /// @todo we could be smarter about this:
851 374
        ///       - not force usage of curl if it is not present
852
        ///       - not force usage of curl for https (minor BC)
853 374
        ///       - use the presence of curl_extra_opts or socket_extra_opts as a hint
854
        $useCurl = ($this->use_curl == self::USE_CURL_ALWAYS) || ($this->use_curl == self::USE_CURL_AUTO && (
855
            in_array($method, array('https', 'http11', 'h2c', 'h2')) ||
856
            ($this->username != '' && $this->authtype != 1) ||
857
            ($this->proxy != '' && $this->proxy_user != '' && $this->proxy_authtype != 1)
858
            // uncomment the following if not forcing curl always for 'https'
859
            //|| ($this->sslversion == 7 && PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION == '7.3')
860
            //|| ($this->sslversion != 0 && PHP_MAJOR_VERSION < 6)
861
        ));
862
863
        // BC - we go through sendPayloadCURL/sendPayloadSocket in case some subclass reimplemented those
864
        if ($useCurl) {
865
            $r = $this->sendPayloadCURL(
0 ignored issues
show
Deprecated Code introduced by
The function PhpXmlRpc\Client::sendPayloadCURL() has been deprecated. ( Ignorable by Annotation )

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

865
            $r = /** @scrutinizer ignore-deprecated */ $this->sendPayloadCURL(
Loading history...
866
                $req,
867
                $this->server,
868
                $this->port,
869
                $timeout,
870
                $this->username,
871
                $this->password,
872
                $this->authtype,
873
                $this->cert,
874
                $this->certpass,
875
                $this->cacert,
876
                $this->cacertdir,
877
                $this->proxy,
878
                $this->proxyport,
879
                $this->proxy_user,
880
                $this->proxy_pass,
881
                $this->proxy_authtype,
882
                // BC
883
                $method == 'http11' ? 'http' : $method,
884
                $this->keepalive,
885
                $this->key,
886 322
                $this->keypass,
887
                $this->sslversion
888
            );
889
        } else {
890
            $r = $this->sendPayloadSocket(
0 ignored issues
show
Deprecated Code introduced by
The function PhpXmlRpc\Client::sendPayloadSocket() has been deprecated. ( Ignorable by Annotation )

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

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

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

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

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

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

1220
    protected function sendViaCURL($req, $method, $server, $port, /** @scrutinizer ignore-unused */ $path, $opts)

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

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

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

1242
        $curl = /** @scrutinizer ignore-deprecated */ $this->prepareCurlHandle($req, $server, $port, $opts['timeout'], $opts['username'], $opts['password'],
Loading history...
1243 45
            $opts['authtype'], $opts['cert'], $opts['certpass'], $opts['cacert'], $opts['cacertdir'], $opts['proxy'],
1244 45
            $opts['proxyport'], $opts['proxy_user'], $opts['proxy_pass'], $opts['proxy_authtype'], $method,
1245
            $opts['keepalive'], $opts['key'], $opts['keypass'], $opts['sslversion']);
1246 45
1247 45
        if (!$curl) {
1248
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] .
1249 45
                ': error during curl initialization. Check php error log for details');
1250 45
        }
1251
1252
        $result = curl_exec($curl);
1253 45
1254
        if ($opts['debug'] > 1) {
1255 45
            $message = "---CURL INFO---\n";
1256
            foreach (curl_getinfo($curl) as $name => $val) {
1257
                if (is_array($val)) {
1258
                    $val = implode("\n", $val);
1259
                }
1260
                $message .= $name . ': ' . $val . "\n";
1261 45
            }
1262
            $message .= '---END---';
1263 45
            $this->getLogger()->debug($message);
1264
        }
1265 45
1266
        if (!$result) {
1267 22
            /// @todo we should use a better check here - what if we get back '' or '0'?
1268 22
1269
            $this->errstr = 'no response';
1270
            $resp = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] .
1271 22
                ': ' . curl_error($curl));
1272 22
            curl_close($curl);
1273
            if ($opts['keepalive']) {
1274
                $this->xmlrpc_curl_handle = null;
1275
            }
1276 22
        } else {
1277 22
            if (!$opts['keepalive']) {
1278 22
                curl_close($curl);
1279 22
            }
1280
            $resp = $req->parseResponse($result, true, $opts['return_type']);
0 ignored issues
show
It seems like $result can also be of type true; however, parameter $data of PhpXmlRpc\Request::parseResponse() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

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

Loading history...
1413
            $headers = array_merge($headers, $this->extra_headers);
1414
        }
1415
1416
        // Fix the HTTP/1.1 417 Expectation Failed Bug (curl by default adds a 'Expect: 100-continue' header when POST
1417
        // size exceeds 1025 bytes, apparently)
1418
        $headers[] = 'Expect:';
1419
1420
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
1421
        // previous note: "timeout is borked" (on some old php/curl versions? It seems to work on 8.1. Maybe the issue
1422
        // has to do with dns resolution...)
1423
        if ($opts['timeout']) {
1424
            curl_setopt($curl, CURLOPT_TIMEOUT, $opts['timeout']);
1425
        }
1426
1427
        switch ($method) {
1428
            case 'http10':
1429
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
1430
                break;
1431
            case 'http11':
1432
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
1433
                break;
1434
            case 'h2c':
1435
                if (defined('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE')) {
1436
                    curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE);
1437
                } else {
1438
                    $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. HTTP2 is not supported by the current PHP/curl install');
1439
                    curl_close($curl);
1440
                    return false;
1441
                }
1442
                break;
1443
            case 'h2':
1444
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
1445
                break;
1446
        }
1447
1448
        if ($opts['username'] && $opts['password']) {
1449
            curl_setopt($curl, CURLOPT_USERPWD, $opts['username'] . ':' . $opts['password']);
1450
            if (defined('CURLOPT_HTTPAUTH')) {
1451
                curl_setopt($curl, CURLOPT_HTTPAUTH, $opts['authtype']);
1452
            } elseif ($opts['authtype'] != 1) {
1453
                $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported by the current PHP/curl install');
1454
                curl_close($curl);
1455
                return false;
1456
            }
1457
        }
1458
1459
        // note: h2c is http2 without the https. No need to have it in this IF
1460
        if ($method == 'https' || $method == 'h2') {
1461
            // set cert file
1462
            if ($opts['cert']) {
1463
                curl_setopt($curl, CURLOPT_SSLCERT, $opts['cert']);
1464
            }
1465
            // set cert password
1466
            if ($opts['certpass']) {
1467
                curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $opts['certpass']);
1468
            }
1469
            // whether to verify remote host's cert
1470
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $opts['verifypeer']);
1471
            // set ca certificates file/dir
1472
            if ($opts['cacert']) {
1473
                curl_setopt($curl, CURLOPT_CAINFO, $opts['cacert']);
1474
            }
1475
            if ($opts['cacertdir']) {
1476
                curl_setopt($curl, CURLOPT_CAPATH, $opts['cacertdir']);
1477
            }
1478
            // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1479
            if ($opts['key']) {
1480
                curl_setopt($curl, CURLOPT_SSLKEY, $opts['key']);
1481
            }
1482
            // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1483
            if ($opts['keypass']) {
1484
                curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $opts['keypass']);
1485
            }
1486
            // whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that
1487
            // it matches the hostname used
1488
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $opts['verifyhost']);
1489
            // allow usage of different SSL versions
1490
            curl_setopt($curl, CURLOPT_SSLVERSION, $opts['sslversion']);
1491
        }
1492
1493
        // proxy info
1494
        if ($opts['proxy']) {
1495
            if ($opts['proxyport'] == 0) {
1496
                $opts['proxyport'] = 8080; // NB: even for HTTPS, local connection is on port 8080
1497
            }
1498
            curl_setopt($curl, CURLOPT_PROXY, $opts['proxy'] . ':' . $opts['proxyport']);
1499
            if ($opts['proxy_user']) {
1500
                curl_setopt($curl, CURLOPT_PROXYUSERPWD, $opts['proxy_user'] . ':' . $opts['proxy_pass']);
1501
                if (defined('CURLOPT_PROXYAUTH')) {
1502
                    curl_setopt($curl, CURLOPT_PROXYAUTH, $opts['proxy_authtype']);
1503
                } elseif ($opts['proxy_authtype'] != 1) {
1504
                    $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported by the current PHP/curl install');
1505
                    curl_close($curl);
1506
                    return false;
1507
                }
1508
            }
1509
        }
1510
1511
        // NB: should we build cookie http headers by hand rather than let CURL do it?
1512
        // NB: the following code does not honour 'expires', 'path' and 'domain' cookie attributes set to client obj by the user...
1513
        if (count($opts['cookies'])) {
1514
            $cookieHeader = '';
1515
            foreach ($opts['cookies'] as $name => $cookie) {
1516
                $cookieHeader .= $name . '=' . $cookie['value'] . '; ';
1517
            }
1518
            curl_setopt($curl, CURLOPT_COOKIE, substr($cookieHeader, 0, -2));
1519
        }
1520
1521
        foreach ($opts['extracurlopts'] as $opt => $val) {
1522
            curl_setopt($curl, $opt, $val);
1523
        }
1524
1525
        if ($opts['debug'] > 1) {
1526
            $this->getLogger()->debug("---SENDING---\n$payload\n---END---");
1527
        }
1528
1529
        return $curl;
1530
    }
1531
1532
    /**
1533
     * Send an array of requests and return an array of responses.
1534
     *
1535
     * Unless $this->no_multicall has been set to true, it will try first to use one single xml-rpc call to server method
1536
     * system.multicall, and revert to sending many successive calls in case of failure.
1537
     * This failure is also stored in $this->no_multicall for subsequent calls.
1538
     * Unfortunately, there is no server error code universally used to denote the fact that multicall is unsupported,
1539
     * so there is no way to reliably distinguish between that and a temporary failure.
1540
     * If you are sure that server supports multicall and do not want to fallback to using many single calls, set the
1541
     * 2np parameter to FALSE.
1542
     *
1543
     * NB: trying to shoehorn extra functionality into existing syntax has resulted
1544
     * in pretty much convoluted code...
1545
     *
1546
     * @param Request[] $reqs an array of Request objects
1547
     * @param bool $noFallback When true, upon receiving an error during multicall, multiple single calls will not be
1548
     *                         attempted.
1549
     *                         Deprecated alternative, was: int - "connection timeout (in seconds). See the details in the
1550
     *                         docs for the send() method". Please use setOption instead to set a timeout
1551
     * @param string $method deprecated. Was: "the http protocol variant to be used. See the details in the docs for the send() method."
1552
     *                       Please use the constructor to set an http protocol variant.
1553
     * @param boolean $fallback deprecated. Was: "when true, upon receiving an error during multicall, multiple single
1554
     *                          calls will be attempted"
1555
     * @return Response[]
1556
     */
1557
    public function multicall($reqs, $timeout = 0, $method = '', $fallback = true)
1558
    {
1559
        // BC
1560
        if (is_bool($timeout) && $fallback === true) {
1561
            $fallback = !$timeout;
1562
            $timeout = 0;
1563
        }
1564
1565
        if ($method == '') {
1566
            $method = $this->method;
1567
        }
1568
1569
        if (!$this->no_multicall) {
1570
            $results = $this->_try_multicall($reqs, $timeout, $method);
1571
            /// @todo how to handle the case of $this->return_type = xml?
1572
            if (is_array($results)) {
1573
                // System.multicall succeeded
1574
                return $results;
1575
            } else {
1576
                // either system.multicall is unsupported by server, or the call failed for some other reason.
1577
                // Feature creep: is there a way to tell apart unsupported multicall from other faults?
1578
                if ($fallback) {
1579
                    // Don't try it next time...
1580
                    $this->no_multicall = true;
1581
                } else {
1582
                    $result = $results;
1583
                }
1584
            }
1585
        } else {
1586
            // override fallback, in case careless user tries to do two
1587
            // opposite things at the same time
1588
            $fallback = true;
1589
        }
1590
1591
        $results = array();
1592
        if ($fallback) {
1593
            // system.multicall is (probably) unsupported by server: emulate multicall via multiple requests
1594
            /// @todo use curl multi_ functions to make this quicker (see the implementation in the parallel.php demo)
1595
            foreach ($reqs as $req) {
1596
                $results[] = $this->send($req, $timeout, $method);
1597
            }
1598
        } else {
1599
            // user does NOT want to fallback on many single calls: since we should always return an array of responses,
1600
            // we return an array with the same error repeated n times
1601
            foreach ($reqs as $req) {
1602
                $results[] = $result;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.
Loading history...
1603
            }
1604
        }
1605
1606
        return $results;
1607
    }
1608
1609
    /**
1610
     * Attempt to boxcar $reqs via system.multicall.
1611
     *
1612
     * @param Request[] $reqs
1613
     * @param int $timeout
1614
     * @param string $method
1615
     * @return Response[]|Response a single Response when the call returned a fault / does not conform to what we expect
1616
     *                             from a multicall response
1617
     */
1618
    private function _try_multicall($reqs, $timeout, $method)
1619
    {
1620
        // Construct multicall request
1621
        $calls = array();
1622
        foreach ($reqs as $req) {
1623
            $call['methodName'] = new Value($req->method(), 'string');
1624
            $numParams = $req->getNumParams();
1625
            $params = array();
1626
            for ($i = 0; $i < $numParams; $i++) {
1627
                $params[$i] = $req->getParam($i);
1628
            }
1629
            $call['params'] = new Value($params, 'array');
1630
            $calls[] = new Value($call, 'struct');
1631
        }
1632
        $multiCall = new static::$requestClass('system.multicall');
1633
        $multiCall->addParam(new Value($calls, 'array'));
1634
1635
        // Attempt RPC call
1636
        $result = $this->send($multiCall, $timeout, $method);
1637
1638
        if ($result->faultCode() != 0) {
1639
            // call to system.multicall failed
1640
            return $result;
1641
        }
1642
1643
        // Unpack responses.
1644
        $rets = $result->value();
1645
        $response = array();
1646
1647
        if ($this->return_type == 'xml') {
1648
            for ($i = 0; $i < count($reqs); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
1649
                $response[] = new static::$responseClass($rets, 0, '', 'xml', $result->httpResponse());
1650
            }
1651
1652
        } elseif ($this->return_type == 'phpvals') {
1653
            if (!is_array($rets)) {
1654
                // bad return type from system.multicall
1655
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1656
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': not an array', 'phpvals', $result->httpResponse());
1657
            }
1658
            $numRets = count($rets);
1659
            if ($numRets != count($reqs)) {
1660
                // wrong number of return values.
1661
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1662
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': incorrect number of responses', 'phpvals',
1663
                    $result->httpResponse());
1664
            }
1665
1666
            for ($i = 0; $i < $numRets; $i++) {
1667
                $val = $rets[$i];
1668
                if (!is_array($val)) {
1669
                    return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1670
                        PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array or struct",
1671
                        'phpvals', $result->httpResponse());
1672
                }
1673
                switch (count($val)) {
1674
                    case 1:
1675
                        if (!isset($val[0])) {
1676
                            // Bad value
1677
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1678
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has no value",
1679
                                'phpvals', $result->httpResponse());
1680
                        }
1681
                        // Normal return value
1682
                        $response[$i] = new static::$responseClass($val[0], 0, '', 'phpvals', $result->httpResponse());
1683
                        break;
1684
                    case 2:
1685
                        /// @todo remove usage of @: it is apparently quite slow
1686
                        $code = @$val['faultCode'];
1687
                        if (!is_int($code)) {
1688
                            /// @todo should we check that it is != 0?
1689
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1690
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1691
                                'phpvals', $result->httpResponse());
1692
                        }
1693
                        $str = @$val['faultString'];
1694
                        if (!is_string($str)) {
1695
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1696
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no FaultString",
1697
                                'phpvals', $result->httpResponse());
1698
                        }
1699
                        $response[$i] = new static::$responseClass(0, $code, $str, 'phpvals', $result->httpResponse());
1700
                        break;
1701
                    default:
1702
                        return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1703
                            PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1704
                            'phpvals', $result->httpResponse());
1705
                }
1706
            }
1707
1708
        } else {
1709
            // return type == 'xmlrpcvals'
1710
            if ($rets->kindOf() != 'array') {
1711
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1712
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array", 'xmlrpcvals',
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $i seems to be defined by a foreach iteration on line 1622. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
1713
                    $result->httpResponse());
1714
            }
1715
            $numRets = $rets->count();
1716
            if ($numRets != count($reqs)) {
1717
                // wrong number of return values.
1718
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1719
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': incorrect number of responses', 'xmlrpcvals',
1720
                    $result->httpResponse());
1721
            }
1722
1723
            foreach ($rets as $i => $val) {
1724
                switch ($val->kindOf()) {
1725
                    case 'array':
1726
                        if ($val->count() != 1) {
1727
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1728
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1729
                                'phpvals', $result->httpResponse());
1730
                        }
1731
                        // Normal return value
1732
                        $response[] = new static::$responseClass($val[0], 0, '', 'xmlrpcvals', $result->httpResponse());
1733
                        break;
1734
                    case 'struct':
1735
                        if ($val->count() != 2) {
1736
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1737
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1738
                                'phpvals', $result->httpResponse());
1739
                        }
1740
                        /** @var Value $code */
1741
                        $code = $val['faultCode'];
1742
                        if ($code->kindOf() != 'scalar' || $code->scalarTyp() != 'int') {
1743
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1744
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1745
                                'xmlrpcvals', $result->httpResponse());
1746
                        }
1747
                        /** @var Value $str */
1748
                        $str = $val['faultString'];
1749
                        if ($str->kindOf() != 'scalar' || $str->scalarTyp() != 'string') {
1750
                            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1751
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1752
                                'xmlrpcvals', $result->httpResponse());
1753
                        }
1754
                        $response[] = new static::$responseClass(0, $code->scalarVal(), $str->scalarVal(), 'xmlrpcvals', $result->httpResponse());
1755
                        break;
1756
                    default:
1757
                        return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1758
                            PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array or struct",
1759
                            'xmlrpcvals', $result->httpResponse());
1760
                }
1761
            }
1762
        }
1763
1764
        return $response;
1765
    }
1766
1767
    // *** BC layer ***
1768
1769
    /**
1770
     * NB: always goes via socket, never curl
1771
     *
1772
     * @deprecated
1773
     *
1774
     * @param Request $req
1775
     * @param string $server
1776
     * @param int $port
1777
     * @param int $timeout
1778
     * @param string $username
1779
     * @param string $password
1780
     * @param int $authType
1781
     * @param string $proxyHost
1782
     * @param int $proxyPort
1783
     * @param string $proxyUsername
1784
     * @param string $proxyPassword
1785
     * @param int $proxyAuthType
1786
     * @param string $method
1787
     * @return Response
1788
     */
1789
    protected function sendPayloadHTTP10($req, $server, $port, $timeout = 0, $username = '', $password = '',
1790
        $authType = 1, $proxyHost = '', $proxyPort = 0, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1,
1791
        $method = 'http')
1792
    {
1793
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
1794
1795
        return $this->sendPayloadSocket($req, $server, $port, $timeout, $username, $password, $authType, null, null,
0 ignored issues
show
Deprecated Code introduced by
The function PhpXmlRpc\Client::sendPayloadSocket() has been deprecated. ( Ignorable by Annotation )

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

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

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

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