Server   F
last analyzed

Complexity

Total Complexity 279

Size/Duplication

Total Lines 1589
Duplicated Lines 0 %

Test Coverage

Coverage 68.89%

Importance

Changes 30
Bugs 2 Features 2
Metric Value
eloc 694
dl 0
loc 1589
rs 1.906
c 30
b 2
f 2
ccs 310
cts 450
cp 0.6889
wmc 279

37 Methods

Rating   Name   Duplication   Size   Complexity  
A setOptions() 0 7 2
A xmlrpc_debugmsg() 0 3 1
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
B getOption() 0 14 9
D parseRequest() 0 90 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
A add_to_map() 0 20 5
A addToMap() 0 4 1
F execute() 0 187 35
C verifySignature() 0 41 12
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 36 10
A _xmlrpcs_listMethods() 0 11 3
C __unset() 0 34 14
F service() 0 90 19
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' (the latter 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). Valid values: 'phpvals', 'xmlrpcvals'
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
            /// @todo allow the user to easily subclass this in a way which allows the resp. headers to be already sent
441
            ///       by now without flagging it as an error. Possibly check for presence of Content-Type header
442
            $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': http headers already sent before response is fully generated. Check for php warning or error messages');
443 561
        }
444 559
445 559
        print $payload;
446 559
447 559
        // return response, in case subclasses want it
448
        return $resp;
449
    }
450
451
    /**
452 561
     * Add a method to the dispatch map.
453 104
     *
454
     * @param string $methodName the name with which the method will be made available
455 457
     * @param callable $function the php function that will get invoked
456
     * @param array[] $sig the array of valid method signatures.
457
     *                     Each element is one signature: an array of strings with at least one element
458 561
     *                     First element = type of returned value. Elements 2..N = types of parameters 1..N
459
     * @param string $doc method documentation
460
     * @param array[] $sigDoc the array of valid method signatures docs, following the format of $sig but with
461 561
     *                        descriptions instead of types (one string for return type, one per param)
462 104
     * @param string $parametersType to allow single method handlers to receive php values instead of a Request, or vice-versa
463
     * @param int $exceptionHandling @see $this->exception_handling
464 104
     * @return void
465 104
     *
466 52
     * @todo raise a warning if the user tries to register a 'system.' method - but allow users to do that
467 52
     */
468 52
    public function addToMap($methodName, $function, $sig = null, $doc = false, $sigDoc = false, $parametersType = false,
469
        $exceptionHandling = false)
470 52
    {
471 52
       $this->add_to_map($methodName, $function, $sig, $doc, $sigDoc, $parametersType, $exceptionHandling);
0 ignored issues
show
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

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

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

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

471
       /** @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 $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

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

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

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

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

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