Passed
Push — master ( 9935a3...ca95a4 )
by Gaetano
06:51
created

Server::addToMap()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 7
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 1
rs 10
1
<?php
2
3
namespace PhpXmlRpc;
4
5
use PhpXmlRpc\Exception\NoSuchMethodException;
6
use PhpXmlRpc\Exception\ValueErrorException;
7
use PhpXmlRpc\Helper\Http;
8
use PhpXmlRpc\Helper\Interop;
9
use PhpXmlRpc\Helper\Logger;
10
use PhpXmlRpc\Helper\XMLParser;
11
use PhpXmlRpc\Traits\CharsetEncoderAware;
12
use PhpXmlRpc\Traits\DeprecationLogger;
13
use PhpXmlRpc\Traits\ParserAware;
14
15
/**
16
 * Allows effortless implementation of XML-RPC servers
17
 *
18
 * @property string[] $accepted_compression deprecated - public access left in purely for BC. Access via getOption()/setOption()
19
 * @property bool $allow_system_funcs deprecated - public access left in purely for BC. Access via getOption()/setOption()
20
 * @property bool $compress_response deprecated - public access left in purely for BC. Access via getOption()/setOption()
21
 * @property int $debug deprecated - public access left in purely for BC. Access via getOption()/setOption()
22
 * @property int $exception_handling deprecated - public access left in purely for BC. Access via getOption()/setOption()
23
 * @property string $functions_parameters_type deprecated - public access left in purely for BC. Access via getOption()/setOption()
24
 * @property array $phpvals_encoding_options deprecated - public access left in purely for BC. Access via getOption()/setOption()
25
 * @property string $response_charset_encoding deprecated - public access left in purely for BC. Access via getOption()/setOption()
26
 */
27
class Server
28
{
29
    use CharsetEncoderAware;
30
    use DeprecationLogger;
31
    use ParserAware;
32
33
    const OPT_ACCEPTED_COMPRESSION = 'accepted_compression';
34
    const OPT_ALLOW_SYSTEM_FUNCS = 'allow_system_funcs';
35
    const OPT_COMPRESS_RESPONSE = 'compress_response';
36
    const OPT_DEBUG = 'debug';
37
    const OPT_EXCEPTION_HANDLING = 'exception_handling';
38
    const OPT_FUNCTIONS_PARAMETERS_TYPE = 'functions_parameters_type';
39
    const OPT_PHPVALS_ENCODING_OPTIONS = 'phpvals_encoding_options';
40
    const OPT_RESPONSE_CHARSET_ENCODING = 'response_charset_encoding';
41
42
    /**
43
     * @var string
44
     * Defines how functions in $dmap will be invoked: either using an xml-rpc Request object or plain php values.
45
     * Valid strings are 'xmlrpcvals', 'phpvals' or 'epivals' (only for use by polyfill-xmlrpc).
46
     *
47
     * @todo create class constants for these
48
     */
49
    protected $functions_parameters_type = 'xmlrpcvals';
50
51
    /**
52
     * @var array
53
     * Option used for fine-tuning the encoding the php values returned from functions registered in the dispatch map
54
     * when the functions_parameters_type member is set to 'phpvals'.
55
     * @see Encoder::encode for a list of values
56
     */
57
    protected $phpvals_encoding_options = array('auto_dates');
58
59
    /**
60
     * @var int
61
     * Controls whether the server is going to echo debugging messages back to the client as comments in response body.
62
     * SECURITY SENSITIVE!
63
     * Valid values:
64
     * 0 =
65
     * 1 =
66
     * 2 =
67
     * 3 =
68
     */
69
    protected $debug = 1;
70
71
    /**
72
     * @var int
73
     * Controls behaviour of server when the invoked method-handler function throws an exception (within the `execute` method):
74
     * 0 = catch it and return an 'internal error' xml-rpc response (default)
75
     * 1 = SECURITY SENSITIVE DO NOT ENABLE ON PUBLIC SERVERS!!! catch it and return an xml-rpc response with the error
76
     *     corresponding to the exception, both its code and message.
77
     * 2 = allow the exception to float to the upper layers
78
     * Can be overridden per-method-handler in the dispatch map
79
     */
80
    protected $exception_handling = 0;
81
82
    /**
83
     * @var bool
84
     * When set to true, it will enable HTTP compression of the response, in case the client has declared its support
85
     * for compression in the request.
86
     * Automatically set at constructor time.
87
     */
88
    protected $compress_response = false;
89
90
    /**
91
     * @var string[]
92
     * List of http compression methods accepted by the server for requests. Automatically set at constructor time.
93
     * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib
94
     */
95
    protected $accepted_compression = array();
96
97
    /**
98
     * @var bool
99
     * Shall we serve calls to system.* methods?
100
     */
101
    protected $allow_system_funcs = true;
102
103
    /**
104
     * List of charset encodings natively accepted for requests.
105
     * Set at constructor time.
106
     * @deprecated UNUSED so far by this library. It is still accessible by subclasses but will be dropped in the future.
107
     */
108
    private $accepted_charset_encodings = array();
109
110
    /**
111
     * @var string
112
     * Charset encoding to be used for response.
113
     * NB: if we can, we will convert the generated response from internal_encoding to the intended one.
114
     * Can be:
115 562
     * - a supported xml encoding (only UTF-8 and ISO-8859-1, unless mbstring is enabled),
116
     * - null (leave unspecified in response, convert output stream to US_ASCII),
117 562
     * - 'auto' (use client-specified charset encoding or same as request if request headers do not specify it (unless request is US-ASCII: then use library default anyway).
118 562
     * NB: pretty dangerous if you accept every charset and do not have mbstring enabled)
119
     */
120 562
    protected $response_charset_encoding = '';
121
122
    protected static $options = array(
123
        self::OPT_ACCEPTED_COMPRESSION,
124
        self::OPT_ALLOW_SYSTEM_FUNCS,
125
        self::OPT_COMPRESS_RESPONSE,
126
        self::OPT_DEBUG,
127
        self::OPT_EXCEPTION_HANDLING,
128 2
        self::OPT_FUNCTIONS_PARAMETERS_TYPE,
129
        self::OPT_PHPVALS_ENCODING_OPTIONS,
130 2
        self::OPT_RESPONSE_CHARSET_ENCODING,
131 2
    );
132
133 2
    /**
134
     * @var mixed
135
     * Extra data passed at runtime to method handling functions. Used only by EPI layer
136
     * @internal
137
     */
138
    public $user_data = null;
139
140
    /**
141
     * Array defining php functions exposed as xml-rpc methods by this server.
142
     * @var array[] $dmap
143
     */
144
    protected $dmap = array();
145
146
    /**
147
     * Storage for internal debug info.
148
     */
149
    protected $debug_info = '';
150
151
    protected static $_xmlrpc_debuginfo = '';
152 562
    protected static $_xmlrpcs_occurred_errors = '';
153
    protected static $_xmlrpcs_prev_ehandler = '';
154
155
    /**
156 562
     * @param array[] $dispatchMap the dispatch map with definition of exposed services
157 562
     *                             Array keys are the names of the method names.
158 562
     *                             Each array value is an array with the following members:
159
     *                             - function (callable)
160
     *                             - docstring (optional)
161
     *                             - signature (array, optional)
162 562
     *                             - signature_docs (array, optional)
163
     *                             - parameters_type (string, optional)
164
     *                             - exception_handling (int, optional)
165
     * @param boolean $serviceNow set to false in order to prevent the server from running upon construction
166
     */
167
    public function __construct($dispatchMap = null, $serviceNow = true)
168
    {
169
        // if ZLIB is enabled, let the server by default accept compressed requests,
170 562
        // and compress responses sent to clients that support them
171 561
        if (function_exists('gzinflate')) {
172 561
            $this->accepted_compression[] = 'gzip';
173 2
        }
174
        if (function_exists('gzuncompress')) {
175
            $this->accepted_compression[] = 'deflate';
176 562
        }
177
        if (function_exists('gzencode') || function_exists('gzcompress')) {
178
            $this->compress_response = true;
179
        }
180
181
        // by default the xml parser can support these 3 charset encodings
182
        $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII');
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Server::$accepted_charset_encodings has been deprecated: UNUSED so far by this library. It is still accessible by subclasses but will be dropped in the future. ( Ignorable by Annotation )

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

182
        /** @scrutinizer ignore-deprecated */ $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII');

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

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

Loading history...
183
184
        // dispMap is a dispatch array of methods mapped to function names and signatures.
185
        // If a method doesn't appear in the map then an unknown method error is generated.
186
        // milosch - changed to make passing dispMap optional. Instead, you can use the class add_to_map() function
187
        // to add functions manually (borrowed from SOAPX4)
188
        if ($dispatchMap) {
189
            $this->setDispatchMap($dispatchMap);
190
            if ($serviceNow) {
191
                $this->service();
192 559
            }
193
        }
194 559
    }
195 559
196
    /**
197
     * @param string $name see all the OPT_ constants
198
     * @param mixed $value
199
     * @return $this
200
     * @throws ValueErrorException on unsupported option
201
     */
202
    public function setOption($name, $value)
203
    {
204 2
        switch ($name) {
205
            case self::OPT_ACCEPTED_COMPRESSION :
206 2
            case self::OPT_ALLOW_SYSTEM_FUNCS:
207 2
            case self::OPT_COMPRESS_RESPONSE:
208
            case self::OPT_DEBUG:
209
            case self::OPT_EXCEPTION_HANDLING:
210
            case self::OPT_FUNCTIONS_PARAMETERS_TYPE:
211
            case self::OPT_PHPVALS_ENCODING_OPTIONS:
212
            case self::OPT_RESPONSE_CHARSET_ENCODING:
213
                $this->$name = $value;
214
                break;
215
            default:
216 22
                throw new ValueErrorException("Unsupported option '$name'");
217
        }
218 22
219 22
        return $this;
220
    }
221
222
    /**
223
     * @param string $name see all the OPT_ constants
224
     * @return mixed
225
     * @throws ValueErrorException on unsupported option
226
     */
227
    public function getOption($name)
228 561
    {
229
        switch ($name) {
230
            case self::OPT_ACCEPTED_COMPRESSION:
231
            case self::OPT_ALLOW_SYSTEM_FUNCS:
232
            case self::OPT_COMPRESS_RESPONSE:
233
            case self::OPT_DEBUG:
234
            case self::OPT_EXCEPTION_HANDLING:
235 561
            case self::OPT_FUNCTIONS_PARAMETERS_TYPE:
236 561
            case self::OPT_PHPVALS_ENCODING_OPTIONS:
237 559
            case self::OPT_RESPONSE_CHARSET_ENCODING:
238
                return $this->$name;
239 561
            default:
240 2
                throw new ValueErrorException("Unsupported option '$name'");
241
        }
242
    }
243
244
    /**
245
     * Returns the complete list of Server options.
246 561
     * @return array
247
     */
248
    public function getOptions()
249
    {
250
        $values = array();
251
        foreach(static::$options as $opt) {
252
            $values[$opt] = $this->getOption($opt);
253
        }
254
        return $values;
255
    }
256
257
    /**
258
     * @param array $options key:  see all the OPT_ constants
259 561
     * @return $this
260
     * @throws ValueErrorException on unsupported option
261 561
     */
262 561
    public function setOptions($options)
263
    {
264 561
        foreach($options as $name => $value) {
265
            $this->setOption($name, $value);
266
        }
267 561
268
        return $this;
269
    }
270 561
271 559
    /**
272
     * Set debug level of server.
273
     *
274 561
     * @param integer $level debug lvl: determines info added to xml-rpc responses (as xml comments)
275 561
     *                    0 = no debug info,
276
     *                    1 = msgs set from user with debugmsg(),
277 561
     *                    2 = add complete xml-rpc request (headers and body),
278
     *                    3 = add also all processing warnings happened during method processing
279
     *                    (NB: this involves setting a custom error handler, and might interfere
280
     *                    with the standard processing of the php function exposed as method. In
281
     *                    particular, triggering a USER_ERROR level error will not halt script
282 561
     *                    execution anymore, but just end up logged in the xml-rpc response)
283
     *                    Note that info added at level 2 and 3 will be base64 encoded
284
     * @return $this
285 561
     */
286 22
    public function setDebug($level)
287 22
    {
288
        $this->debug = $level;
289
        return $this;
290 561
    }
291 561
292 561
    /**
293
     * Add a string to the debug info that can be later serialized by the server as part of the response message.
294
     * Note that for best compatibility, the debug string should be encoded using the PhpXmlRpc::$xmlrpc_internalencoding
295
     * character set.
296 561
     *
297 561
     * @param string $msg
298
     * @return void
299 561
     */
300
    public static function xmlrpc_debugmsg($msg)
301 561
    {
302
        static::$_xmlrpc_debuginfo .= $msg . "\n";
303
    }
304
305
    /**
306
     * Add a string to the debug info that will be later serialized by the server as part of the response message
307 561
     * (base64 encoded) when debug level >= 2
308 561
     *
309
     * @param string $msg
310
     * @return void
311 561
     */
312
    public static function error_occurred($msg)
313
    {
314
        static::$_xmlrpcs_occurred_errors .= $msg . "\n";
315
    }
316
317 561
    /**
318 561
     * Return a string with the serialized representation of all debug info.
319 104
     *
320
     * @internal this function will become protected in the future
321 104
     *
322 52
     * @param string $charsetEncoding the target charset encoding for the serialization
323 52
     *
324 52
     * @return string an XML comment (or two)
325 52
     */
326 52
    public function serializeDebug($charsetEncoding = '')
327 52
    {
328 52
        // Tough encoding problem: which internal charset should we assume for debug info?
329
        // It might contain a copy of raw data received from client, ie with unknown encoding,
330
        // intermixed with php generated data and user generated data...
331
        // so we split it: system debug is base 64 encoded,
332
        // user debug info should be encoded by the end user using the INTERNAL_ENCODING
333
        $out = '';
334
        if ($this->debug_info != '') {
335
            $out .= "<!-- SERVER DEBUG INFO (BASE64 ENCODED):\n" . base64_encode($this->debug_info) . "\n-->\n";
336 561
        }
337 561
        if (static::$_xmlrpc_debuginfo != '') {
338
            $out .= "<!-- DEBUG INFO:\n" . $this->getCharsetEncoder()->encodeEntities(str_replace('--', '_-', static::$_xmlrpc_debuginfo), PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "\n-->\n";
339
            // NB: a better solution MIGHT be to use CDATA, but we need to insert it
340
            // into return payload AFTER the beginning tag
341
            //$out .= "<![CDATA[ DEBUG INFO:\n\n" . str_replace(']]>', ']_]_>', static::$_xmlrpc_debuginfo) . "\n]]>\n";
342
        }
343 561
344
        return $out;
345
    }
346 561
347
    /**
348
     * Execute the xml-rpc request, printing the response.
349
     *
350
     * @param string $data the request body. If null, the http POST request will be examined
351
     * @param bool $returnPayload When true, return the response but do not echo it or any http header
352
     *
353
     * @return Response|string the response object (usually not used by caller...) or its xml serialization
354
     * @throws \Exception in case the executed method does throw an exception (and depending on server configuration)
355
     */
356
    public function service($data = null, $returnPayload = false)
357
    {
358
        if ($data === null) {
359
            $data = file_get_contents('php://input');
360
        }
361
        $rawData = $data;
362
363
        // reset internal debug info
364
        $this->debug_info = '';
365
366
        // Save what we received, before parsing it
367
        if ($this->debug > 1) {
368
            $this->debugMsg("+++GOT+++\n" . $data . "\n+++END+++");
369
        }
370
371
        $resp = $this->parseRequestHeaders($data, $reqCharset, $respCharset, $respEncoding);
372
        if (!$resp) {
373
            // this actually executes the request
374
            $resp = $this->parseRequest($data, $reqCharset);
375
376
            // save full body of request into response, for debugging purposes.
377
            // NB: this is the _request_ data, not the response's own data, unlike what happens client-side
378
            /// @todo try to move this injection to the resp. constructor or use a non-deprecated access method. Or, even
379
            ///       better: just avoid setting this, and set debug info of the received http request in the request
380
            ///       object instead? It's not like the developer misses access to _SERVER, _COOKIES though...
381
            ///       Last but not least: the raw data might be of use to handler functions - but in decompressed form...
382
            $resp->raw_data = $rawData;
383
        }
384
385
        if ($this->debug > 2 && static::$_xmlrpcs_occurred_errors != '') {
386 536
            $this->debugMsg("+++PROCESSING ERRORS AND WARNINGS+++\n" .
387
                static::$_xmlrpcs_occurred_errors . "+++END+++");
388
        }
389 536
390 536
        $header = $resp->xml_header($respCharset);
391
        if ($this->debug > 0) {
392
            $header .= $this->serializeDebug($respCharset);
393
        }
394 536
395 536
        // Do not create response serialization if it has already happened. Helps to build json magic
396 536
        /// @todo what if the payload was created targeting a different charset than $respCharset?
397 536
        ///       Also, if we do not call serialize(), the request will not set its content-type to have the charset declared
398 515
        $payload = $resp->getPayload();
399 515
        if (empty($payload)) {
400 515
            $payload = $resp->serialize($respCharset);
401 452
        }
402
        $payload = $header . $payload;
403 148
404
        if ($returnPayload) {
405
            return $payload;
406
        }
407
408
        // if we get a warning/error that has output some text before here, then we cannot
409
        // add a new header. We cannot say we are sending xml, either...
410 515
        if (!headers_sent()) {
411 22
            header('Content-Type: ' . $resp->getContentType());
412 22
            // we do not know if client actually told us an accepted charset, but if it did we have to tell it what we did
413 22
            header("Vary: Accept-Charset");
414 22
415 22
            // http compression of output: only if we can do it, and we want to do it, and client asked us to,
416
            // and php ini settings do not force it already
417
            $phpNoSelfCompress = !ini_get('zlib.output_compression') && (ini_get('output_handler') != 'ob_gzhandler');
418 536
            if ($this->compress_response && $respEncoding != '' && $phpNoSelfCompress) {
419 536
                if (strpos($respEncoding, 'gzip') !== false && function_exists('gzencode')) {
420
                    $payload = gzencode($payload);
421
                    header("Content-Encoding: gzip");
422
                    header("Vary: Accept-Encoding");
423 22
                } elseif (strpos($respEncoding, 'deflate') !== false && function_exists('gzcompress')) {
424
                    $payload = gzcompress($payload);
425
                    header("Content-Encoding: deflate");
426 22
                    header("Vary: Accept-Encoding");
427
                }
428
            }
429
430
            // Do not output content-length header if php is compressing output for us: it will mess up measurements.
431
            // Note that Apache/mod_php will add (and even alter!) the Content-Length header on its own, but only for
432
            // responses up to 8000 bytes
433
            if ($phpNoSelfCompress) {
434
                header('Content-Length: ' . (int)strlen($payload));
435 561
            }
436
        } else {
437
            $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': http headers already sent before response is fully generated. Check for php warning or error messages');
438
        }
439 561
440
        print $payload;
441
442
        // return response, in case subclasses want it
443 561
        return $resp;
444 559
    }
445 559
446 559
    /**
447 559
     * Add a method to the dispatch map.
448
     *
449
     * @param string $methodName the name with which the method will be made available
450
     * @param callable $function the php function that will get invoked
451
     * @param array[] $sig the array of valid method signatures.
452 561
     *                     Each element is one signature: an array of strings with at least one element
453 104
     *                     First element = type of returned value. Elements 2..N = types of parameters 1..N
454
     * @param string $doc method documentation
455 457
     * @param array[] $sigDoc the array of valid method signatures docs, following the format of $sig but with
456
     *                        descriptions instead of types (one string for return type, one per param)
457
     * @param string $parametersType to allow single method handlers to receive php values instead of a Request, or vice-versa
458 561
     * @param int $exceptionHandling @see $this->exception_handling
459
     * @return void
460
     *
461 561
     * @todo raise a warning if the user tries to register a 'system.' method
462 104
     */
463
    public function addToMap($methodName, $function, $sig = null, $doc = false, $sigDoc = false, $parametersType = false,
464 104
        $exceptionHandling = false)
465 104
    {
466 52
       $this->add_to_map($methodName, $function, $sig, $doc, $sigDoc, $parametersType, $exceptionHandling);
0 ignored issues
show
Bug introduced by
It seems like $sigDoc can also be of type false; however, parameter $sigDoc of PhpXmlRpc\Server::add_to_map() does only seem to accept array<mixed,array>, 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

466
       $this->add_to_map($methodName, $function, $sig, $doc, /** @scrutinizer ignore-type */ $sigDoc, $parametersType, $exceptionHandling);
Loading history...
Bug introduced by
It seems like $doc can also be of type false; however, parameter $doc of PhpXmlRpc\Server::add_to_map() 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

466
       $this->add_to_map($methodName, $function, $sig, /** @scrutinizer ignore-type */ $doc, $sigDoc, $parametersType, $exceptionHandling);
Loading history...
Deprecated Code introduced by
The function PhpXmlRpc\Server::add_to_map() has been deprecated: use addToMap instead ( Ignorable by Annotation )

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

466
       /** @scrutinizer ignore-deprecated */ $this->add_to_map($methodName, $function, $sig, $doc, $sigDoc, $parametersType, $exceptionHandling);

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

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

Loading history...
Bug introduced by
It seems like $exceptionHandling can also be of type false; however, parameter $exceptionHandling of PhpXmlRpc\Server::add_to_map() does only seem to accept integer, 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

466
       $this->add_to_map($methodName, $function, $sig, $doc, $sigDoc, $parametersType, /** @scrutinizer ignore-type */ $exceptionHandling);
Loading history...
Bug introduced by
It seems like $parametersType can also be of type false; however, parameter $parametersType of PhpXmlRpc\Server::add_to_map() 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

466
       $this->add_to_map($methodName, $function, $sig, $doc, $sigDoc, /** @scrutinizer ignore-type */ $parametersType, $exceptionHandling);
Loading history...
467 52
    }
468 52
469
    /**
470 52
     * Add a method to the dispatch map.
471 52
     *
472 52
     * @param string $methodName the name with which the method will be made available
473 52
     * @param callable $function the php function that will get invoked
474
     * @param array[] $sig the array of valid method signatures.
475
     *                     Each element is one signature: an array of strings with at least one element
476
     *                     First element = type of returned value. Elements 2..N = types of parameters 1..N
477
     * @param string $doc method documentation
478
     * @param array[] $sigDoc the array of valid method signatures docs, following the format of $sig but with
479
     *                        descriptions instead of types (one string for return type, one per param)
480
     * @param string $parametersType to allow single method handlers to receive php values instead of a Request, or vice-versa
481
     * @param int $exceptionHandling @see $this->exception_handling
482
     * @return void
483
     *
484
     * @todo raise a warning if the user tries to register a 'system.' method
485
     * @deprecated use addToMap instead
486
     */
487
    public function add_to_map($methodName, $function, $sig = null, $doc = false, $sigDoc = false, $parametersType = false,
488
        $exceptionHandling = false)
489
    {
490
        $this->logDeprecationUnlessCalledBy('addToMap');
491
492
        $this->dmap[$methodName] = array(
493
            'function' => $function,
494 561
            'docstring' => $doc,
495
        );
496
        if ($sig) {
497
            $this->dmap[$methodName]['signature'] = $sig;
498
        }
499
        if ($sigDoc) {
500
            $this->dmap[$methodName]['signature_docs'] = $sigDoc;
501
        }
502
        if ($parametersType) {
503
            $this->dmap[$methodName]['parameters_type'] = $parametersType;
504
        }
505
        if ($exceptionHandling !== false) {
506
            $this->dmap[$methodName]['exception_handling'] = $exceptionHandling;
507
        }
508
    }
509
510
    /**
511
     * Verify type and number of parameters received against a list of known signatures.
512
     *
513
     * @param array|Request $in array of either xml-rpc value objects or xml-rpc type definitions
514
     * @param array $sigs array of known signatures to match against
515
     * @return array int, string
516 561
     */
517
    protected function verifySignature($in, $sigs)
518
    {
519 561
        // check each possible signature in turn
520 104
        if (is_object($in)) {
521
            $numParams = $in->getNumParams();
522 457
        } else {
523
            $numParams = count($in);
524
        }
525
        foreach ($sigs as $curSig) {
526
            if (count($curSig) == $numParams + 1) {
527 561
                $itsOK = 1;
528
                for ($n = 0; $n < $numParams; $n++) {
529
                    if (is_object($in)) {
530 561
                        $p = $in->getParam($n);
531
                        if ($p->kindOf() == 'scalar') {
532
                            $pt = $p->scalarTyp();
533
                        } else {
534
                            $pt = $p->kindOf();
535
                        }
536
                    } else {
537
                        $pt = ($in[$n] == 'i4') ? 'int' : strtolower($in[$n]); // dispatch maps never use i4...
538
                    }
539
540
                    // param index is $n+1, as first member of sig is return type
541
                    if ($pt != $curSig[$n + 1] && $curSig[$n + 1] != Value::$xmlrpcValue) {
542
                        $itsOK = 0;
543
                        $pno = $n + 1;
544
                        $wanted = $curSig[$n + 1];
545
                        $got = $pt;
546
                        break;
547 562
                    }
548
                }
549
                if ($itsOK) {
550
                    return array(1, '');
551 562
                }
552
            }
553
        }
554
        if (isset($wanted)) {
555
            return array(0, "Wanted {$wanted}, got {$got} at param {$pno}");
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $got does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $pno does not seem to be defined for all execution paths leading up to this point.
Loading history...
556
        } else {
557
            return array(0, "No method signature matches number of parameters");
558
        }
559
    }
560 561
561 4
    /**
562 2
     * Parse http headers received along with xml-rpc request. If needed, inflate request.
563
     *
564 2
     * @return Response|null null on success or an error Response
565 2
     */
566
    protected function parseRequestHeaders(&$data, &$reqEncoding, &$respEncoding, &$respCompression)
567
    {
568
        // check if $_SERVER is populated: it might have been disabled via ini file
569
        // (this is true even when in CLI mode)
570
        if (count($_SERVER) == 0) {
571
            $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': cannot parse request headers as $_SERVER is not populated');
572
        }
573
574
        if ($this->debug > 1) {
575
            if (function_exists('getallheaders')) {
576
                $this->debugMsg(''); // empty line
577 562
                foreach (getallheaders() as $name => $val) {
578
                    $this->debugMsg("HEADER: $name: $val");
579
                }
580
            }
581 562
        }
582
583
        if (isset($_SERVER['HTTP_CONTENT_ENCODING'])) {
584 562
            $contentEncoding = str_replace('x-', '', $_SERVER['HTTP_CONTENT_ENCODING']);
585 562
        } else {
586 562
            $contentEncoding = '';
587
        }
588 2
589 2
        $rawData = $data;
590 2
591 2
        // check if request body has been compressed and decompress it
592 560
        if ($contentEncoding != '' && strlen($data)) {
593 1
            if ($contentEncoding == 'deflate' || $contentEncoding == 'gzip') {
594 1
                // if decoding works, use it. else assume data wasn't gzencoded
595 1
                /// @todo test separately for gzinflate and gzuncompress
596
                if (function_exists('gzinflate') && in_array($contentEncoding, $this->accepted_compression)) {
597
                    if ($contentEncoding == 'deflate' && $degzdata = @gzuncompress($data)) {
598
                        $data = $degzdata;
599
                        if ($this->debug > 1) {
600
                            $this->debugMsg("\n+++INFLATED REQUEST+++[" . strlen($data) . " chars]+++\n" . $data . "\n+++END+++");
601 559
                        }
602 559
                    } elseif ($contentEncoding == 'gzip' && $degzdata = @gzinflate(substr($data, 10))) {
603
                        $data = $degzdata;
604
                        if ($this->debug > 1) {
605
                            $this->debugMsg("+++INFLATED REQUEST+++[" . strlen($data) . " chars]+++\n" . $data . "\n+++END+++");
606
                        }
607
                    } else {
608
                        $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_decompress_fail'],
609
                            PhpXmlRpc::$xmlrpcstr['server_decompress_fail'], '', array('raw_data' => $rawData)
610
                        );
611
612 559
                        return $r;
613
                    }
614 559
                } else {
615 538
                    $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_cannot_decompress'],
616
                        PhpXmlRpc::$xmlrpcstr['server_cannot_decompress'], '', array('raw_data' => $rawData)
617
                    );
618 559
619 559
                    return $r;
620
                }
621 559
            }
622
        }
623
624
        // check if client specified accepted charsets, and if we know how to fulfill the request
625 562
        if ($this->response_charset_encoding == 'auto') {
626
            $respEncoding = '';
627
            if (isset($_SERVER['HTTP_ACCEPT_CHARSET'])) {
628
                // here we check if we can match the client-requested encoding with the encodings we know we can generate.
629
                // we parse q=0.x preferences instead of preferring the first charset specified
630
                $http = new Http();
631
                $clientAcceptedCharsets = $http->parseAcceptHeader($_SERVER['HTTP_ACCEPT_CHARSET']);
632
                $knownCharsets = $this->getCharsetEncoder()->knownCharsets();
633
                foreach ($clientAcceptedCharsets as $accepted) {
634
                    foreach ($knownCharsets as $charset) {
635
                        if (strtoupper($accepted) == strtoupper($charset)) {
636
                            $respEncoding = $charset;
637
                            break 2;
638
                        }
639 559
                    }
640
                }
641 559
            }
642 559
        } else {
643
            $respEncoding = $this->response_charset_encoding;
644 559
        }
645 559
646
        if (isset($_SERVER['HTTP_ACCEPT_ENCODING'])) {
647
            $respCompression = $_SERVER['HTTP_ACCEPT_ENCODING'];
648
        } else {
649 559
            $respCompression = '';
650 559
        }
651
652 559
        // 'guestimate' request encoding
653
        /// @todo check if mbstring is enabled and automagic input conversion is on: it might mingle with this check???
654 85
        $reqEncoding = XMLParser::guessEncoding(isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : '',
655 85
            $data);
656 85
657
        return null;
658
    }
659
660 559
    /**
661 536
     * Parse an xml chunk containing an xml-rpc request and execute the corresponding php function registered with the
662 536
     * server.
663 536
     * @internal this function will become protected in the future
664
     *
665
     * @param string $data the xml request
666
     * @param string $reqEncoding (optional) the charset encoding of the xml request
667 536
     * @return Response
668
     * @throws \Exception in case the executed method does throw an exception (and depending on server configuration)
669 22
     *
670 22
     * @todo either rename this function or move the 'execute' part out of it...
671 22
     */
672 22
    public function parseRequest($data, $reqEncoding = '')
673
    {
674
        // decompose incoming XML into request structure
675
676
        /// @todo move this block of code into the XMLParser
677 559
        if ($reqEncoding != '') {
678
            // Since parsing will fail if
679 559
            // - charset is not specified in the xml declaration,
680 127
            // - the encoding is not UTF8 and
681
            // - there are non-ascii chars in the text,
682
            // we try to work round that...
683 559
            // The following code might be better for mb_string enabled installs, but it makes the lib about 200% slower...
684 150
            //if (!is_valid_charset($reqEncoding, array('UTF-8')))
685 24
            if (!in_array($reqEncoding, array('UTF-8', 'US-ASCII')) && !XMLParser::hasEncoding($data)) {
686
                if (function_exists('mb_convert_encoding')) {
687 127
                    $data = mb_convert_encoding($data, 'UTF-8', $reqEncoding);
688
                } else {
689 431
                    if ($reqEncoding == 'ISO-8859-1') {
690 129
                        $data = utf8_encode($data);
691
                    } else {
692 324
                        $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': unsupported charset encoding of received request: ' . $reqEncoding);
693
                    }
694
                }
695
            }
696 559
        }
697
        // PHP internally might use ISO-8859-1, so we have to tell the xml parser to give us back data in the expected charset.
698
        // What if internal encoding is not in one of the 3 allowed? We use the broadest one, i.e. utf8
699
        if (in_array(PhpXmlRpc::$xmlrpc_internalencoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) {
700
            $options = array(XML_OPTION_TARGET_ENCODING => PhpXmlRpc::$xmlrpc_internalencoding);
701
        } else {
702
            $options = array(XML_OPTION_TARGET_ENCODING => 'UTF-8', 'target_charset' => PhpXmlRpc::$xmlrpc_internalencoding);
703
        }
704
        // register a callback with the xml parser for when it finds the method name
705
        $options['methodname_callback'] = array($this, 'methodNameCallback');
706
707 559
        $xmlRpcParser = $this->getParser();
708 559
        try {
709
            $_xh = $xmlRpcParser->parse($data, $this->functions_parameters_type, XMLParser::ACCEPT_REQUEST, $options);
710
            // BC
711
            if (!is_array($_xh)) {
712
                $_xh = $xmlRpcParser->_xh;
713 559
            }
714 559
        } catch (NoSuchMethodException $e) {
715 127
            return new Response(0, $e->getCode(), $e->getMessage());
716
        }
717 454
718
        if ($_xh['isf'] == 3) {
719 557
            // (BC) we return XML error as a faultCode
720
            preg_match('/^XML error ([0-9]+)/', $_xh['isf_reason'], $matches);
721
            return new Response(
722
                0,
723
                PhpXmlRpc::$xmlrpcerrxml + (int)$matches[1],
724
                $_xh['isf_reason']);
725
        } elseif ($_xh['isf']) {
726
            /// @todo separate better the various cases, as we have done in Request::parseResponse: invalid xml-rpc vs.
727
            ///       parsing error
728
            return new Response(
729
                0,
730
                PhpXmlRpc::$xmlrpcerr['invalid_request'],
731
                PhpXmlRpc::$xmlrpcstr['invalid_request'] . ' ' . $_xh['isf_reason']);
732
        } else {
733
            // small layering violation in favor of speed and memory usage: we should allow the 'execute' method handle
734
            // this, but in the most common scenario (xml-rpc values type server with some methods registered as phpvals)
735
            // that would mean a useless encode+decode pass
736
            if ($this->functions_parameters_type != 'xmlrpcvals' ||
737
                (isset($this->dmap[$_xh['method']]['parameters_type']) &&
738
                    ($this->dmap[$_xh['method']]['parameters_type'] != 'xmlrpcvals')
739
                )
740
            ) {
741
                if ($this->debug > 1) {
742
                    $this->debugMsg("\n+++PARSED+++\n" . var_export($_xh['params'], true) . "\n+++END+++");
743
                }
744
745
                return $this->execute($_xh['method'], $_xh['params'], $_xh['pt']);
746
            } else {
747
                // build a Request object with data parsed from xml and add parameters in
748
                $req = new Request($_xh['method']);
749
                for ($i = 0; $i < count($_xh['params']); $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...
750
                    $req->addParam($_xh['params'][$i]);
751
                }
752
753
                if ($this->debug > 1) {
754
                    $this->debugMsg("\n+++PARSED+++\n" . var_export($req, true) . "\n+++END+++");
755
                }
756
757
                return $this->execute($req);
758
            }
759
        }
760
    }
761
762 45
    /**
763
     * Execute a method invoked by the client, checking parameters used.
764
     *
765 45
     * @param Request|string $req either a Request obj or a method name
766 45
     * @param mixed[] $params array with method parameters as php types (only if $req is method name)
767
     * @param string[] $paramTypes array with xml-rpc types of method parameters (only if $req is method name)
768
     * @return Response
769
     *
770
     * @throws \Exception in case the executed method does throw an exception (and depending on server configuration)
771
     */
772
    protected function execute($req, $params = null, $paramTypes = null)
773
    {
774
        static::$_xmlrpcs_occurred_errors = '';
775 45
        static::$_xmlrpc_debuginfo = '';
776 2
777 2
        if (is_object($req)) {
778
            $methodName = $req->method();
779 45
        } else {
780
            $methodName = $req;
781
        }
782 559
783
        $sysCall = $this->isSyscall($methodName);
784
        $dmap = $sysCall ? $this->getSystemDispatchMap() : $this->dmap;
785 559
786 64
        if (!isset($dmap[$methodName]['function'])) {
787
            // No such method
788 496
            return new Response(0, PhpXmlRpc::$xmlrpcerr['unknown_method'], PhpXmlRpc::$xmlrpcstr['unknown_method']);
789
        }
790
791
        // Check signature
792 559
        if (isset($dmap[$methodName]['signature'])) {
793
            $sig = $dmap[$methodName]['signature'];
794
            if (is_object($req)) {
795
                list($ok, $errStr) = $this->verifySignature($req, $sig);
796
            } else {
797
                list($ok, $errStr) = $this->verifySignature($paramTypes, $sig);
798
            }
799
            if (!$ok) {
800 559
                // Didn't match.
801
                return new Response(
802 559
                    0,
803 559
                    PhpXmlRpc::$xmlrpcerr['incorrect_params'],
804
                    PhpXmlRpc::$xmlrpcstr['incorrect_params'] . ": {$errStr}"
805
                );
806
            }
807
        }
808
809 561
        $func = $dmap[$methodName]['function'];
810
811 561
        // let the 'class::function' syntax be accepted in dispatch maps
812 52
        if (is_string($func) && strpos($func, '::')) {
813
            $func = explode('::', $func);
814 509
        }
815
816
        // build string representation of function 'name'
817
        if (is_array($func)) {
818
            if (is_object($func[0])) {
819
                $funcName = get_class($func[0]) . '->' . $func[1];
820
            } else {
821
                $funcName = implode('::', $func);
822 559
            }
823
        } else if ($func instanceof \Closure) {
824 559
            $funcName = 'Closure';
825
        } else {
826
            $funcName = $func;
827
        }
828
829
        // verify that function to be invoked is in fact callable
830
        if (!is_callable($func)) {
831
            $this->getLogger()->error("XML-RPC: " . __METHOD__ . ": function '$funcName' registered as method handler is not callable");
832
            return new Response(
833
                0,
834
                PhpXmlRpc::$xmlrpcerr['server_error'],
835
                PhpXmlRpc::$xmlrpcstr['server_error'] . ": no function matches method"
836
            );
837
        }
838 127
839
        if (isset($dmap[$methodName]['exception_handling'])) {
840 127
            $exception_handling = (int)$dmap[$methodName]['exception_handling'];
841
        } else {
842
            $exception_handling = $this->exception_handling;
843
        }
844
845
        // If debug level is 3, we should catch all errors generated during processing of user function, and log them
846 127
        // as part of response
847
        if ($this->debug > 2) {
848
            self::$_xmlrpcs_prev_ehandler = set_error_handler(array('\PhpXmlRpc\Server', '_xmlrpcs_errorHandler'));
849 127
        }
850 127
851
        try {
852
            // Allow mixed-convention servers
853
            if (is_object($req)) {
854 127
                // call an 'xml-rpc aware' function
855 127
                if ($sysCall) {
856 127
                    $r = call_user_func($func, $this, $req);
857
                } else {
858
                    $r = call_user_func($func, $req);
859
                }
860 127
                if (!is_a($r, 'PhpXmlRpc\Response')) {
861 127
                    $this->getLogger()->error("XML-RPC: " . __METHOD__ . ": function '$funcName' registered as method handler does not return an xmlrpc response object but a " . gettype($r));
862 127
                    if (is_a($r, 'PhpXmlRpc\Value')) {
863
                        $r = new Response($r);
864
                    } else {
865
                        $r = new Response(
866 127
                            0,
867 127
                            PhpXmlRpc::$xmlrpcerr['server_error'],
868 127
                            PhpXmlRpc::$xmlrpcstr['server_error'] . ": function does not return xmlrpc response object"
869
                        );
870
                    }
871
                }
872 127
            } else {
873 127
                // call a 'plain php' function
874 127
                if ($sysCall) {
875
                    array_unshift($params, $this);
0 ignored issues
show
Bug introduced by
It seems like $params can also be of type null; however, parameter $array of array_unshift() does only seem to accept array, 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

875
                    array_unshift(/** @scrutinizer ignore-type */ $params, $this);
Loading history...
876
                    $r = call_user_func_array($func, $params);
877
                } else {
878
                    // 3rd API convention for method-handling functions: EPI-style
879
                    if ($this->functions_parameters_type == 'epivals') {
880
                        $r = call_user_func_array($func, array($methodName, $params, $this->user_data));
881
                        // mimic EPI behaviour: if we get an array that looks like an error, make it an error response
882
                        if (is_array($r) && array_key_exists('faultCode', $r) && array_key_exists('faultString', $r)) {
883
                            $r = new Response(0, (integer)$r['faultCode'], (string)$r['faultString']);
884
                        } else {
885
                            // functions using EPI api should NOT return resp objects, so make sure we encode the
886
                            // return type correctly
887
                            $encoder = new Encoder();
888
                            $r = new Response($encoder->encode($r, array('extension_api')));
889
                        }
890
                    } else {
891
                        $r = call_user_func_array($func, $params);
0 ignored issues
show
Bug introduced by
It seems like $params can also be of type null; however, parameter $args of call_user_func_array() does only seem to accept array, 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

891
                        $r = call_user_func_array($func, /** @scrutinizer ignore-type */ $params);
Loading history...
892
                    }
893
                }
894
                // the return type can be either a Response object or a plain php value...
895
                if (!is_a($r, '\PhpXmlRpc\Response')) {
896
                    // q: what should we assume here about automatic encoding of datetimes and php classes instances?
897
                    // a: let the user decide
898
                    $encoder = new Encoder();
899
                    $r = new Response($encoder->encode($r, $this->phpvals_encoding_options));
900
                }
901
            }
902
        /// @todo bump minimum php version to 7.1 and use a single catch clause instead of the duplicate blocks
903
        } catch (\Exception $e) {
904
            // (barring errors in the lib) an uncaught exception happened in the called function, we wrap it in a
905
            // proper error-response
906
            switch ($exception_handling) {
907
                case 2:
908
                    if ($this->debug > 2) {
909
                        if (self::$_xmlrpcs_prev_ehandler) {
910
                            set_error_handler(self::$_xmlrpcs_prev_ehandler);
911
                        } else {
912
                            restore_error_handler();
913
                        }
914
                    }
915
                    throw $e;
916
                case 1:
917
                    $errCode = $e->getCode();
918
                    if ($errCode == 0) {
919
                        $errCode = PhpXmlRpc::$xmlrpcerr['server_error'];
920
                    }
921
                    $r = new Response(0, $errCode, $e->getMessage());
922
                    break;
923
                default:
924
                    $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_error'], PhpXmlRpc::$xmlrpcstr['server_error']);
925
            }
926
        } catch (\Error $e) {
927
            // (barring errors in the lib) an uncaught exception happened in the called function, we wrap it in a
928
            // proper error-response
929
            switch ($exception_handling) {
930
                case 2:
931
                    if ($this->debug > 2) {
932
                        if (self::$_xmlrpcs_prev_ehandler) {
933 22
                            set_error_handler(self::$_xmlrpcs_prev_ehandler);
934
                        } else {
935 22
                            restore_error_handler();
936 22
                        }
937 22
                    }
938
                    throw $e;
939 22
                case 1:
940 22
                    $errCode = $e->getCode();
941
                    if ($errCode == 0) {
942
                        $errCode = PhpXmlRpc::$xmlrpcerr['server_error'];
943 22
                    }
944
                    $r = new Response(0, $errCode, $e->getMessage());
945
                    break;
946
                default:
947
                    $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_error'], PhpXmlRpc::$xmlrpcstr['server_error']);
948
            }
949
        }
950
951 106
        if ($this->debug > 2) {
952
            // note: restore the error handler we found before calling the user func, even if it has been changed
953
            // inside the func itself
954 106
            if (self::$_xmlrpcs_prev_ehandler) {
955 106
                set_error_handler(self::$_xmlrpcs_prev_ehandler);
956 106
            } else {
957
                restore_error_handler();
958
            }
959
        }
960 106
961 85
        return $r;
962
    }
963 22
964
    /**
965 106
     * Registered as callback for when the XMLParser has found the name of the method to execute.
966 106
     * Handling that early allows to 1. stop parsing the rest of the xml if there is no such method registered, and
967 106
     * 2. tweak the type of data that the parser will return, in case the server uses mixed-calling-convention
968 106
     *
969 106
     * @internal
970 106
     * @param $methodName
971 106
     * @param XMLParser $xmlParser
972
     * @param resource $parser
973 106
     * @return void
974
     * @throws NoSuchMethodException
975 106
     *
976
     * @todo feature creep - we could validate here that the method in the dispatch map is valid, but that would mean
977
     *       dirtying a lot the logic, as we would have back to both parseRequest() and execute() methods the info
978
     *       about the matched method handler, in order to avoid doing the work twice...
979
     */
980
    public function methodNameCallback($methodName, $xmlParser, $parser)
981
    {
982 1
        $sysCall = $this->isSyscall($methodName);
983
        $dmap = $sysCall ? $this->getSystemDispatchMap() : $this->dmap;
984
985 106
        if (!isset($dmap[$methodName]['function'])) {
986
            // No such method
987
            throw new NoSuchMethodException(PhpXmlRpc::$xmlrpcstr['unknown_method'], PhpXmlRpc::$xmlrpcerr['unknown_method']);
988
        }
989
990
        // alter on-the-fly the config of the xml parser if needed
991
        if (isset($dmap[$methodName]['parameters_type']) &&
992
            $dmap[$methodName]['parameters_type'] != $this->functions_parameters_type) {
993 85
            /// @todo this should be done by a method of the XMLParser
994
            switch ($dmap[$methodName]['parameters_type']) {
995
                case XMLParser::RETURN_PHP:
996 85
                    xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast');
997 85
                    break;
998 85
                case XMLParser::RETURN_EPIVALS:
999
                    xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_epi');
1000
                    break;
1001
                /// @todo log a warning on unsupported return type
1002 85
                case XMLParser::RETURN_XMLRPCVALS:
1003 85
                default:
1004
                    xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee');
1005 1
            }
1006
        }
1007 85
    }
1008 85
1009 85
    /**
1010
     * Add a string to the 'internal debug message' (separate from 'user debug message').
1011
     *
1012
     * @param string $string
1013
     * @return void
1014
     */
1015
    protected function debugMsg($string)
1016
    {
1017 85
        $this->debug_info .= $string . "\n";
1018
    }
1019
1020 64
    /**
1021
     * @param string $methName
1022 64
     * @return bool
1023 64
     */
1024 64
    protected function isSyscall($methName)
1025
    {
1026 64
        return (strpos($methName, "system.") === 0);
1027 64
    }
1028
1029 64
    /**
1030 64
     * @param array $dmap
1031 64
     * @return $this
1032
     */
1033 64
    public function setDispatchMap($dmap)
1034
    {
1035
        $this->dmap = $dmap;
1036
        return $this;
1037
    }
1038
1039
    /**
1040
     * @return array[]
1041 64
     */
1042
    public function getDispatchMap()
1043 64
    {
1044
        return $this->dmap;
1045
    }
1046 64
1047 64
    /**
1048
     * @return array[]
1049
     */
1050 64
    public function getSystemDispatchMap()
1051
    {
1052
        if (!$this->allow_system_funcs) {
1053 64
            return array();
1054 64
        }
1055
1056
        return array(
1057 64
            'system.listMethods' => array(
1058 64
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_listMethods',
1059
                // listMethods: signature was either a string, or nothing.
1060
                // The useless string variant has been removed
1061 64
                'signature' => array(array(Value::$xmlrpcArray)),
1062
                'docstring' => 'This method lists all the methods that the XML-RPC server knows how to dispatch',
1063
                'signature_docs' => array(array('list of method names')),
1064
            ),
1065 64
            'system.methodHelp' => array(
1066 64
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_methodHelp',
1067 64
                'signature' => array(array(Value::$xmlrpcString, Value::$xmlrpcString)),
1068
                'docstring' => 'Returns help text if defined for the method passed, otherwise returns an empty string',
1069
                'signature_docs' => array(array('method description', 'name of the method to be described')),
1070
            ),
1071
            'system.methodSignature' => array(
1072
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_methodSignature',
1073
                'signature' => array(array(Value::$xmlrpcArray, Value::$xmlrpcString)),
1074
                'docstring' => 'Returns an array of known signatures (an array of arrays) for the method name passed. If no signatures are known, returns a none-array (test for type != array to detect missing signature)',
1075 64
                'signature_docs' => array(array('list of known signatures, each sig being an array of xmlrpc type names', 'name of method to be described')),
1076
            ),
1077 64
            'system.multicall' => array(
1078 64
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_multicall',
1079
                'signature' => array(array(Value::$xmlrpcArray, Value::$xmlrpcArray)),
1080
                'docstring' => 'Boxcar multiple RPC calls in one request. See http://www.xmlrpc.com/discuss/msgReader$1208 for details',
1081 64
                'signature_docs' => array(array('list of response structs, where each struct has the usual members', 'list of calls, with each call being represented as a struct, with members "methodname" and "params"')),
1082
            ),
1083
            'system.getCapabilities' => array(
1084
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_getCapabilities',
1085
                'signature' => array(array(Value::$xmlrpcStruct)),
1086
                'docstring' => 'This method lists all the capabilities that the XML-RPC server has: the (more or less standard) extensions to the xmlrpc spec that it adheres to',
1087
                'signature_docs' => array(array('list of capabilities, described as structs with a version number and url for the spec')),
1088
            ),
1089
        );
1090
    }
1091
1092
    /**
1093
     * @return array[]
1094
     */
1095
    public function getCapabilities()
1096
    {
1097
        $outAr = array(
1098
            // xml-rpc spec: always supported
1099
            'xmlrpc' => array(
1100
                'specUrl' => 'http://www.xmlrpc.com/spec', // NB: the spec sits now at http://xmlrpc.com/spec.md
1101
                'specVersion' => 1
1102
            ),
1103
            // if we support system.xxx functions, we always support multicall, too...
1104
            'system.multicall' => array(
1105
                // Note that, as of 2006/09/17, the following URL does not respond anymore
1106
                'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
1107
                'specVersion' => 1
1108
            ),
1109
            // introspection: version 2! we support 'mixed', too.
1110
            // note: the php xml-rpc extension says this instead:
1111
            //   url http://xmlrpc-epi.sourceforge.net/specs/rfc.introspection.php, version 20010516
1112
            'introspection' => array(
1113
                'specUrl' => 'http://phpxmlrpc.sourceforge.net/doc-2/ch10.html',
1114
                'specVersion' => 2,
1115
            ),
1116
        );
1117
1118
        // NIL extension
1119
        if (PhpXmlRpc::$xmlrpc_null_extension) {
1120
            $outAr['nil'] = array(
1121
                // Note that, as of 2023/01, the following URL does not respond anymore
1122
                'specUrl' => 'http://www.ontosys.com/xml-rpc/extensions.php',
1123
                'specVersion' => 1
1124
            );
1125
        }
1126
1127
        // support for "standard" error codes
1128
        if (PhpXmlRpc::$xmlrpcerr['unknown_method'] === Interop::$xmlrpcerr['unknown_method']) {
1129
            $outAr['faults_interop'] = array(
1130
                'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
1131
                'specVersion' => 20010516
1132
            );
1133
        }
1134
1135
        return $outAr;
1136
    }
1137 85
1138
    /**
1139 85
     * @internal handler of a system. method
1140
     *
1141 85
     * @param Server $server
1142 85
     * @param Request $req
1143 85
     * @return Response
1144 64
     */
1145
    public static function _xmlrpcs_getCapabilities($server, $req = null)
0 ignored issues
show
Unused Code introduced by
The parameter $req 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

1145
    public static function _xmlrpcs_getCapabilities($server, /** @scrutinizer ignore-unused */ $req = null)

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...
1146
    {
1147
        $encoder = new Encoder();
1148
        return new Response($encoder->encode($server->getCapabilities()));
1149
    }
1150
1151
    /**
1152
     * @internal handler of a system. method
1153 85
     *
1154
     * @param Server $server
1155
     * @param Request $req if called in plain php values mode, second param is missing
1156
     * @return Response
1157
     */
1158
    public static function _xmlrpcs_listMethods($server, $req = null)
0 ignored issues
show
Unused Code introduced by
The parameter $req 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

1158
    public static function _xmlrpcs_listMethods($server, /** @scrutinizer ignore-unused */ $req = null)

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...
1159
    {
1160
        $outAr = array();
1161
        foreach ($server->dmap as $key => $val) {
1162
            $outAr[] = new Value($key, 'string');
1163
        }
1164 43
        foreach ($server->getSystemDispatchMap() as $key => $val) {
1165
            $outAr[] = new Value($key, 'string');
1166
        }
1167 43
1168 22
        return new Response(new Value($outAr, 'array'));
1169
    }
1170
1171
    /**
1172 22
     * @internal handler of a system. method
1173 22
     *
1174
     * @param Server $server
1175
     * @param Request $req
1176
     * @return Response
1177 22
     */
1178
    public static function _xmlrpcs_methodSignature($server, $req)
1179
    {
1180 22
        // let's accept as parameter either an xml-rpc value or string
1181 22
        if (is_object($req)) {
1182 22
            $methName = $req->getParam(0);
1183
            $methName = $methName->scalarVal();
1184 22
        } else {
1185
            $methName = $req;
1186
        }
1187
        if ($server->isSyscall($methName)) {
1188
            $dmap = $server->getSystemDispatchMap();
1189
        } else {
1190
            $dmap = $server->dmap;
1191
        }
1192
        if (isset($dmap[$methName])) {
1193
            if (isset($dmap[$methName]['signature'])) {
1194
                $sigs = array();
1195
                foreach ($dmap[$methName]['signature'] as $inSig) {
1196
                    $curSig = array();
1197
                    foreach ($inSig as $sig) {
1198 22
                        $curSig[] = new Value($sig, 'string');
1199
                    }
1200
                    $sigs[] = new Value($curSig, 'array');
1201
                }
1202
                $r = new Response(new Value($sigs, 'array'));
1203
            } else {
1204
                // NB: according to the official docs, we should be returning a
1205
                // "none-array" here, which means not-an-array
1206
                $r = new Response(new Value('undef', 'string'));
1207
            }
1208
        } else {
1209
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['introspect_unknown'], PhpXmlRpc::$xmlrpcstr['introspect_unknown']);
1210
        }
1211
1212
        return $r;
1213
    }
1214
1215
    /**
1216
     * @internal handler of a system. method
1217
     *
1218
     * @param Server $server
1219
     * @param Request $req
1220
     * @return Response
1221
     */
1222
    public static function _xmlrpcs_methodHelp($server, $req)
1223
    {
1224
        // let's accept as parameter either an xml-rpc value or string
1225
        if (is_object($req)) {
1226
            $methName = $req->getParam(0);
1227
            $methName = $methName->scalarVal();
1228
        } else {
1229
            $methName = $req;
1230
        }
1231
        if ($server->isSyscall($methName)) {
1232
            $dmap = $server->getSystemDispatchMap();
1233
        } else {
1234
            $dmap = $server->dmap;
1235
        }
1236
        if (isset($dmap[$methName])) {
1237
            if (isset($dmap[$methName]['docstring'])) {
1238
                $r = new Response(new Value($dmap[$methName]['docstring'], 'string'));
1239
            } else {
1240
                $r = new Response(new Value('', 'string'));
1241
            }
1242
        } else {
1243
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['introspect_unknown'], PhpXmlRpc::$xmlrpcstr['introspect_unknown']);
1244
        }
1245
1246
        return $r;
1247
    }
1248
1249
    /**
1250
     * @internal this function will become protected in the future
1251
     *
1252
     * @param $err
1253
     * @return Value
1254
     */
1255
    public static function _xmlrpcs_multicall_error($err)
1256
    {
1257
        if (is_string($err)) {
1258
            $str = PhpXmlRpc::$xmlrpcstr["multicall_{$err}"];
1259
            $code = PhpXmlRpc::$xmlrpcerr["multicall_{$err}"];
1260
        } else {
1261
            $code = $err->faultCode();
1262
            $str = $err->faultString();
1263
        }
1264
        $struct = array();
1265
        $struct['faultCode'] = new Value($code, 'int');
1266
        $struct['faultString'] = new Value($str, 'string');
1267
1268
        return new Value($struct, 'struct');
1269
    }
1270
1271
    /**
1272
     * @internal this function will become protected in the future
1273
     *
1274
     * @param Server $server
1275
     * @param Value $call
1276
     * @return Value
1277
     */
1278
    public static function _xmlrpcs_multicall_do_call($server, $call)
1279
    {
1280
        if ($call->kindOf() != 'struct') {
1281
            return static::_xmlrpcs_multicall_error('notstruct');
1282
        }
1283
        $methName = @$call['methodName'];
1284
        if (!$methName) {
1285
            return static::_xmlrpcs_multicall_error('nomethod');
1286
        }
1287
        if ($methName->kindOf() != 'scalar' || $methName->scalarTyp() != 'string') {
1288
            return static::_xmlrpcs_multicall_error('notstring');
1289
        }
1290
        if ($methName->scalarVal() == 'system.multicall') {
1291
            return static::_xmlrpcs_multicall_error('recursion');
1292
        }
1293
1294
        $params = @$call['params'];
1295
        if (!$params) {
1296
            return static::_xmlrpcs_multicall_error('noparams');
1297
        }
1298
        if ($params->kindOf() != 'array') {
1299
            return static::_xmlrpcs_multicall_error('notarray');
1300
        }
1301
1302
        $req = new Request($methName->scalarVal());
1303
        foreach ($params as $i => $param) {
1304
            if (!$req->addParam($param)) {
1305
                $i++; // for error message, we count params from 1
1306
                return static::_xmlrpcs_multicall_error(new Response(0,
1307
                    PhpXmlRpc::$xmlrpcerr['incorrect_params'],
1308
                    PhpXmlRpc::$xmlrpcstr['incorrect_params'] . ": probable xml error in param " . $i));
1309
            }
1310
        }
1311
1312
        $result = $server->execute($req);
1313
1314
        if ($result->faultCode() != 0) {
1315
            return static::_xmlrpcs_multicall_error($result); // Method returned fault.
1316
        }
1317
1318
        return new Value(array($result->value()), 'array');
1319
    }
1320
1321
    /**
1322
     * @internal this function will become protected in the future
1323
     *
1324
     * @param Server $server
1325
     * @param Value $call
1326
     * @return Value
1327
     */
1328
    public static function _xmlrpcs_multicall_do_call_phpvals($server, $call)
1329
    {
1330
        if (!is_array($call)) {
0 ignored issues
show
introduced by
The condition is_array($call) is always false.
Loading history...
1331
            return static::_xmlrpcs_multicall_error('notstruct');
1332
        }
1333
        if (!array_key_exists('methodName', $call)) {
1334
            return static::_xmlrpcs_multicall_error('nomethod');
1335
        }
1336
        if (!is_string($call['methodName'])) {
1337
            return static::_xmlrpcs_multicall_error('notstring');
1338
        }
1339
        if ($call['methodName'] == 'system.multicall') {
1340
            return static::_xmlrpcs_multicall_error('recursion');
1341
        }
1342
        if (!array_key_exists('params', $call)) {
1343
            return static::_xmlrpcs_multicall_error('noparams');
1344
        }
1345
        if (!is_array($call['params'])) {
1346
            return static::_xmlrpcs_multicall_error('notarray');
1347
        }
1348
1349
        // this is a simplistic hack, since we might have received
1350
        // base64 or datetime values, but they will be listed as strings here...
1351
        $pt = array();
1352
        $wrapper = new Wrapper();
1353
        foreach ($call['params'] as $val) {
1354
            // support EPI-encoded base64 and datetime values
1355
            if ($val instanceof \stdClass && isset($val->xmlrpc_type)) {
1356
                $pt[] = $val->xmlrpc_type == 'datetime' ? Value::$xmlrpcDateTime : $val->xmlrpc_type;
1357
            } else {
1358
                $pt[] = $wrapper->php2XmlrpcType(gettype($val));
1359
            }
1360
        }
1361
1362
        $result = $server->execute($call['methodName'], $call['params'], $pt);
1363
1364
        if ($result->faultCode() != 0) {
1365
            return static::_xmlrpcs_multicall_error($result); // Method returned fault.
1366
        }
1367
1368
        return new Value(array($result->value()), 'array');
1369
    }
1370
1371
    /**
1372
     * @internal handler of a system. method
1373
     *
1374
     * @param Server $server
1375
     * @param Request|array $req
1376
     * @return Response
1377
     */
1378
    public static function _xmlrpcs_multicall($server, $req)
1379
    {
1380
        $result = array();
1381
        // let's accept a plain list of php parameters, beside a single xml-rpc msg object
1382
        if (is_object($req)) {
1383
            $calls = $req->getParam(0);
1384
            foreach ($calls as $call) {
1385
                $result[] = static::_xmlrpcs_multicall_do_call($server, $call);
1386
            }
1387
        } else {
1388
            $numCalls = count($req);
1389
            for ($i = 0; $i < $numCalls; $i++) {
1390
                $result[$i] = static::_xmlrpcs_multicall_do_call_phpvals($server, $req[$i]);
1391
            }
1392
        }
1393
1394
        return new Response(new Value($result, 'array'));
1395
    }
1396
1397
    /**
1398
     * Error handler used to track errors that occur during server-side execution of PHP code.
1399
     * This allows to report back to the client whether an internal error has occurred or not
1400
     * using an xml-rpc response object, instead of letting the client deal with the html junk
1401
     * that a PHP execution error on the server generally entails.
1402
     *
1403
     * NB: in fact a user defined error handler can only handle WARNING, NOTICE and USER_* errors.
1404
     *
1405
     * @internal
1406
     */
1407
    public static function _xmlrpcs_errorHandler($errCode, $errString, $filename = null, $lineNo = null, $context = null)
1408
    {
1409
        // obey the @ protocol
1410
        if (error_reporting() == 0) {
1411
            return;
1412
        }
1413
1414
        //if ($errCode != E_NOTICE && $errCode != E_WARNING && $errCode != E_USER_NOTICE && $errCode != E_USER_WARNING)
1415
        if ($errCode != E_STRICT) {
1416
            static::error_occurred($errString);
1417
        }
1418
1419
        // Try to avoid as much as possible disruption to the previous error handling mechanism in place
1420
        if (self::$_xmlrpcs_prev_ehandler == '') {
1421
            // The previous error handler was the default: all we should do is log error to the default error log
1422
            // (if level high enough)
1423
            if (ini_get('log_errors') && (intval(ini_get('error_reporting')) & $errCode)) {
1424
                // we can't use the functionality of LoggerAware, because this is a static method
1425
                if (self::$logger === null) {
1426
                    self::$logger = Logger::instance();
1427
                }
1428
                self::$logger->error($errString);
1429
            }
1430
        } else {
1431
            // Pass control on to previous error handler, trying to avoid loops...
1432
            if (self::$_xmlrpcs_prev_ehandler != array('\PhpXmlRpc\Server', '_xmlrpcs_errorHandler')) {
0 ignored issues
show
introduced by
The condition self::_xmlrpcs_prev_ehan..._xmlrpcs_errorHandler') is always true.
Loading history...
1433
                if (is_array(self::$_xmlrpcs_prev_ehandler)) {
0 ignored issues
show
introduced by
The condition is_array(self::_xmlrpcs_prev_ehandler) is always false.
Loading history...
1434
                    // the following works both with static class methods and plain object methods as error handler
1435
                    call_user_func_array(self::$_xmlrpcs_prev_ehandler, array($errCode, $errString, $filename, $lineNo, $context));
1436
                } else {
1437
                    $method = self::$_xmlrpcs_prev_ehandler;
1438
                    $method($errCode, $errString, $filename, $lineNo, $context);
1439
                }
1440
            }
1441
        }
1442
    }
1443
1444
    // *** BC layer ***
1445
1446
    /**
1447
     * @param string $charsetEncoding
1448
     * @return string
1449
     *
1450
     * @deprecated this method was moved to the Response class
1451
     */
1452
    protected function xml_header($charsetEncoding = '')
1453
    {
1454
        $this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
1455
1456
        if ($charsetEncoding != '') {
1457
            return "<?xml version=\"1.0\" encoding=\"$charsetEncoding\"?" . ">\n";
1458
        } else {
1459
            return "<?xml version=\"1.0\"?" . ">\n";
1460
        }
1461
    }
1462
1463
    // we have to make this return by ref in order to allow calls such as `$resp->_cookies['name'] = ['value' => 'something'];`
1464
    public function &__get($name)
1465
    {
1466
        switch ($name) {
1467
            case self::OPT_ACCEPTED_COMPRESSION :
1468
            case self::OPT_ALLOW_SYSTEM_FUNCS:
1469
            case self::OPT_COMPRESS_RESPONSE:
1470
            case self::OPT_DEBUG:
1471
            case self::OPT_EXCEPTION_HANDLING:
1472
            case self::OPT_FUNCTIONS_PARAMETERS_TYPE:
1473
            case self::OPT_PHPVALS_ENCODING_OPTIONS:
1474
            case self::OPT_RESPONSE_CHARSET_ENCODING:
1475
                $this->logDeprecation('Getting property Request::' . $name . ' is deprecated');
1476
                return $this->$name;
1477
            case 'accepted_charset_encodings':
1478
                // manually implement the 'protected property' behaviour
1479
                $canAccess = false;
1480
                $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
1481
                if (isset($trace[1]) && isset($trace[1]['class'])) {
1482
                    if (is_subclass_of($trace[1]['class'], 'PhpXmlRpc\Server')) {
1483
                        $canAccess = true;
1484
                    }
1485
                }
1486
                if ($canAccess) {
1487
                    $this->logDeprecation('Getting property Request::' . $name . ' is deprecated');
1488
                    return $this->accepted_compression;
1489
                } else {
1490
                    trigger_error("Cannot access protected property Server::accepted_charset_encodings in " . __FILE__, E_USER_ERROR);
1491
                }
1492
                break;
1493
            default:
1494
                /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout...
1495
                $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
1496
                trigger_error('Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
1497
                $result = null;
1498
                return $result;
1499
        }
1500
    }
1501
1502
    public function __set($name, $value)
1503
    {
1504
        switch ($name) {
1505
            case self::OPT_ACCEPTED_COMPRESSION :
1506
            case self::OPT_ALLOW_SYSTEM_FUNCS:
1507
            case self::OPT_COMPRESS_RESPONSE:
1508
            case self::OPT_DEBUG:
1509
            case self::OPT_EXCEPTION_HANDLING:
1510
            case self::OPT_FUNCTIONS_PARAMETERS_TYPE:
1511
            case self::OPT_PHPVALS_ENCODING_OPTIONS:
1512
            case self::OPT_RESPONSE_CHARSET_ENCODING:
1513
                $this->logDeprecation('Setting property Request::' . $name . ' is deprecated');
1514
                $this->$name = $value;
1515
                break;
1516
            case 'accepted_charset_encodings':
1517
                // manually implement the 'protected property' behaviour
1518
                $canAccess = false;
1519
                $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
1520
                if (isset($trace[1]) && isset($trace[1]['class'])) {
1521
                    if (is_subclass_of($trace[1]['class'], 'PhpXmlRpc\Server')) {
1522
                        $canAccess = true;
1523
                    }
1524
                }
1525
                if ($canAccess) {
1526
                    $this->logDeprecation('Setting property Request::' . $name . ' is deprecated');
1527
                    $this->accepted_compression = $value;
1528
                } else {
1529
                    trigger_error("Cannot access protected property Server::accepted_charset_encodings in " . __FILE__, E_USER_ERROR);
1530
                }
1531
                break;
1532
            default:
1533
                /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout...
1534
                $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
1535
                trigger_error('Undefined property via __set(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
1536
        }
1537
    }
1538
1539
    public function __isset($name)
1540
    {
1541
        switch ($name) {
1542
            case self::OPT_ACCEPTED_COMPRESSION :
1543
            case self::OPT_ALLOW_SYSTEM_FUNCS:
1544
            case self::OPT_COMPRESS_RESPONSE:
1545
            case self::OPT_DEBUG:
1546
            case self::OPT_EXCEPTION_HANDLING:
1547
            case self::OPT_FUNCTIONS_PARAMETERS_TYPE:
1548
            case self::OPT_PHPVALS_ENCODING_OPTIONS:
1549
            case self::OPT_RESPONSE_CHARSET_ENCODING:
1550
                $this->logDeprecation('Checking property Request::' . $name . ' is deprecated');
1551
                return isset($this->$name);
1552
            case 'accepted_charset_encodings':
1553
                // manually implement the 'protected property' behaviour
1554
                $canAccess = false;
1555
                $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
1556
                if (isset($trace[1]) && isset($trace[1]['class'])) {
1557
                    if (is_subclass_of($trace[1]['class'], 'PhpXmlRpc\Server')) {
1558
                        $canAccess = true;
1559
                    }
1560
                }
1561
                if ($canAccess) {
1562
                    $this->logDeprecation('Checking property Request::' . $name . ' is deprecated');
1563
                    return isset($this->accepted_compression);
1564
                }
1565
                // break through voluntarily
1566
            default:
1567
                return false;
1568
        }
1569
    }
1570
1571
    public function __unset($name)
1572
    {
1573
        switch ($name) {
1574
            case self::OPT_ACCEPTED_COMPRESSION :
1575
            case self::OPT_ALLOW_SYSTEM_FUNCS:
1576
            case self::OPT_COMPRESS_RESPONSE:
1577
            case self::OPT_DEBUG:
1578
            case self::OPT_EXCEPTION_HANDLING:
1579
            case self::OPT_FUNCTIONS_PARAMETERS_TYPE:
1580
            case self::OPT_PHPVALS_ENCODING_OPTIONS:
1581
            case self::OPT_RESPONSE_CHARSET_ENCODING:
1582
                $this->logDeprecation('Unsetting property Request::' . $name . ' is deprecated');
1583
                unset($this->$name);
1584
                break;
1585
            case 'accepted_charset_encodings':
1586
                // manually implement the 'protected property' behaviour
1587
                $canAccess = false;
1588
                $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
1589
                if (isset($trace[1]) && isset($trace[1]['class'])) {
1590
                    if (is_subclass_of($trace[1]['class'], 'PhpXmlRpc\Server')) {
1591
                        $canAccess = true;
1592
                    }
1593
                }
1594
                if ($canAccess) {
1595
                    $this->logDeprecation('Unsetting property Request::' . $name . ' is deprecated');
1596
                    unset($this->accepted_compression);
1597
                } else {
1598
                    trigger_error("Cannot access protected property Server::accepted_charset_encodings in " . __FILE__, E_USER_ERROR);
1599
                }
1600
                break;
1601
            default:
1602
                /// @todo throw instead? There are very few other places where the lib trigger errors which can potentially reach stdout...
1603
                $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
1604
                trigger_error('Undefined property via __unset(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_WARNING);
1605
        }
1606
    }
1607
}
1608