Server   F
last analyzed

Complexity

Total Complexity 278

Size/Duplication

Total Lines 1580
Duplicated Lines 0 %

Test Coverage

Coverage 68.89%

Importance

Changes 30
Bugs 3 Features 2
Metric Value
eloc 692
c 30
b 3
f 2
dl 0
loc 1580
ccs 310
cts 450
cp 0.6889
rs 1.908
wmc 278

37 Methods

Rating   Name   Duplication   Size   Complexity  
A setOptions() 0 7 2
A xmlrpc_debugmsg() 0 3 1
A add_to_map() 0 20 5
A addToMap() 0 4 1
C verifySignature() 0 41 12
B __construct() 0 25 7
A getOptions() 0 7 2
A serializeDebug() 0 19 3
B setOption() 0 18 9
A error_occurred() 0 3 1
A setDebug() 0 4 1
F service() 0 88 19
B getOption() 0 14 9
D parseRequest() 0 87 17
F parseRequestHeaders() 0 93 25
A debugMsg() 0 3 1
A getSystemDispatchMap() 0 38 2
C _xmlrpcs_multicall_do_call_phpvals() 0 41 12
C __get() 0 35 14
F execute() 0 187 35
A _xmlrpcs_methodHelp() 0 25 5
B methodNameCallback() 0 25 8
C __isset() 0 29 14
A isSyscall() 0 3 1
B _xmlrpcs_methodSignature() 0 35 7
A getDispatchMap() 0 3 1
A _xmlrpcs_multicall_error() 0 14 2
A _xmlrpcs_getCapabilities() 0 4 1
B _xmlrpcs_multicall_do_call() 0 41 11
C __set() 0 34 14
A getCapabilities() 0 41 3
A _xmlrpcs_multicall() 0 17 4
A xml_header() 0 8 2
B _xmlrpcs_errorHandler() 0 32 9
A _xmlrpcs_listMethods() 0 11 3
C __unset() 0 34 14
A setDispatchMap() 0 4 1

How to fix   Complexity   

Complex Class

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

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

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

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
    /** @var string */
43
    protected static $responseClass = '\\PhpXmlRpc\\Response';
44
45
    /**
46
     * @var string
47
     * Defines how functions in $dmap will be invoked: either using an xml-rpc Request object or plain php values.
48
     * Valid strings are 'xmlrpcvals', 'phpvals' or 'epivals' (only for use by polyfill-xmlrpc).
49
     *
50
     * @todo create class constants for these
51
     */
52
    protected $functions_parameters_type = 'xmlrpcvals';
53
54
    /**
55
     * @var array
56
     * Option used for fine-tuning the encoding the php values returned from functions registered in the dispatch map
57
     * when the functions_parameters_type member is set to 'phpvals'.
58
     * @see Encoder::encode for a list of values
59
     */
60
    protected $phpvals_encoding_options = array('auto_dates');
61
62
    /**
63
     * @var int
64
     * Controls whether the server is going to echo debugging messages back to the client as comments in response body.
65
     * SECURITY SENSITIVE!
66
     * Valid values:
67
     * 0 =
68
     * 1 =
69
     * 2 =
70
     * 3 =
71
     */
72
    protected $debug = 1;
73
74
    /**
75
     * @var int
76
     * Controls behaviour of server when the invoked method-handler function throws an exception (within the `execute` method):
77
     * 0 = catch it and return an 'internal error' xml-rpc response (default)
78
     * 1 = SECURITY SENSITIVE DO NOT ENABLE ON PUBLIC SERVERS!!! catch it and return an xml-rpc response with the error
79
     *     corresponding to the exception, both its code and message.
80
     * 2 = allow the exception to float to the upper layers
81
     * Can be overridden per-method-handler in the dispatch map
82
     */
83
    protected $exception_handling = 0;
84
85
    /**
86
     * @var bool
87
     * When set to true, it will enable HTTP compression of the response, in case the client has declared its support
88
     * for compression in the request.
89
     * Automatically set at constructor time.
90
     */
91
    protected $compress_response = false;
92
93
    /**
94
     * @var string[]
95
     * List of http compression methods accepted by the server for requests. Automatically set at constructor time.
96
     * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib
97
     */
98
    protected $accepted_compression = array();
99
100
    /**
101
     * @var bool
102
     * Shall we serve calls to system.* methods?
103
     */
104
    protected $allow_system_funcs = true;
105
106
    /**
107
     * List of charset encodings natively accepted for requests.
108
     * Set at constructor time.
109
     * @deprecated UNUSED so far by this library. It is still accessible by subclasses but will be dropped in the future.
110
     */
111
    private $accepted_charset_encodings = array();
112
113
    /**
114
     * @var string
115 562
     * Charset encoding to be used for response.
116
     * NB: if we can, we will convert the generated response from internal_encoding to the intended one.
117 562
     * Can be:
118 562
     * - a supported xml encoding (only UTF-8 and ISO-8859-1, unless mbstring is enabled),
119
     * - null (leave unspecified in response, convert output stream to US_ASCII),
120 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).
121
     * NB: pretty dangerous if you accept every charset and do not have mbstring enabled)
122
     */
123
    protected $response_charset_encoding = '';
124
125
    protected static $options = array(
126
        self::OPT_ACCEPTED_COMPRESSION,
127
        self::OPT_ALLOW_SYSTEM_FUNCS,
128 2
        self::OPT_COMPRESS_RESPONSE,
129
        self::OPT_DEBUG,
130 2
        self::OPT_EXCEPTION_HANDLING,
131 2
        self::OPT_FUNCTIONS_PARAMETERS_TYPE,
132
        self::OPT_PHPVALS_ENCODING_OPTIONS,
133 2
        self::OPT_RESPONSE_CHARSET_ENCODING,
134
    );
135
136
    /**
137
     * @var mixed
138
     * Extra data passed at runtime to method handling functions. Used only by EPI layer
139
     * @internal
140
     */
141
    public $user_data = null;
142
143
    /**
144
     * Array defining php functions exposed as xml-rpc methods by this server.
145
     * @var array[] $dmap
146
     */
147
    protected $dmap = array();
148
149
    /**
150
     * Storage for internal debug info.
151
     */
152 562
    protected $debug_info = '';
153
154
    protected static $_xmlrpc_debuginfo = '';
155
    protected static $_xmlrpcs_occurred_errors = '';
156 562
    protected static $_xmlrpcs_prev_ehandler = '';
157 562
158 562
    /**
159
     * @param array[] $dispatchMap the dispatch map with definition of exposed services
160
     *                             Array keys are the names of the method names.
161
     *                             Each array value is an array with the following members:
162 562
     *                             - function (callable)
163
     *                             - docstring (optional)
164
     *                             - signature (array, optional)
165
     *                             - signature_docs (array, optional)
166
     *                             - parameters_type (string, optional)
167
     *                             - exception_handling (int, optional)
168
     * @param boolean $serviceNow set to false in order to prevent the server from running upon construction
169
     */
170 562
    public function __construct($dispatchMap = null, $serviceNow = true)
171 561
    {
172 561
        // if ZLIB is enabled, let the server by default accept compressed requests,
173 2
        // and compress responses sent to clients that support them
174
        if (function_exists('gzinflate')) {
175
            $this->accepted_compression[] = 'gzip';
176 562
        }
177
        if (function_exists('gzuncompress')) {
178
            $this->accepted_compression[] = 'deflate';
179
        }
180
        if (function_exists('gzencode') || function_exists('gzcompress')) {
181
            $this->compress_response = true;
182
        }
183
184
        // by default the xml parser can support these 3 charset encodings
185
        $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

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

469
       /** @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

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

469
       $this->add_to_map($methodName, $function, $sig, $doc, $sigDoc, /** @scrutinizer ignore-type */ $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

469
       $this->add_to_map($methodName, $function, $sig, /** @scrutinizer ignore-type */ $doc, $sigDoc, $parametersType, $exceptionHandling);
Loading history...
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

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

880
                    array_unshift(/** @scrutinizer ignore-type */ $params, $this);
Loading history...
881
                    $r = call_user_func_array($func, $params);
882
                } else {
883
                    // 3rd API convention for method-handling functions: EPI-style
884
                    if ($this->functions_parameters_type == 'epivals') {
885
                        $r = call_user_func_array($func, array($methodName, $params, $this->user_data));
886
                        // mimic EPI behaviour: if we get an array that looks like an error, make it an error response
887
                        if (is_array($r) && array_key_exists('faultCode', $r) && array_key_exists('faultString', $r)) {
888
                            $r = new static::$responseClass(0, (integer)$r['faultCode'], (string)$r['faultString']);
889
                        } else {
890
                            // functions using EPI api should NOT return resp objects, so make sure we encode the
891
                            // return type correctly
892
                            $encoder = new Encoder();
893
                            $r = new static::$responseClass($encoder->encode($r, array('extension_api')));
894
                        }
895
                    } else {
896
                        $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

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

1147
    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...
1148
    {
1149
        $encoder = new Encoder();
1150
        return new static::$responseClass($encoder->encode($server->getCapabilities()));
1151
    }
1152
1153 85
    /**
1154
     * @internal handler of a system. method
1155
     *
1156
     * @param Server $server
1157
     * @param Request $req if called in plain php values mode, second param is missing
1158
     * @return Response
1159
     */
1160
    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

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