Passed
Push — master ( b2f8af...6338bd )
by Gaetano
06:21
created

Server::service()   B

Complexity

Conditions 7
Paths 32

Size

Total Lines 44
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 12.2913

Importance

Changes 6
Bugs 2 Features 1
Metric Value
cc 7
eloc 18
c 6
b 2
f 1
nc 32
nop 2
dl 0
loc 44
ccs 11
cts 21
cp 0.5238
crap 12.2913
rs 8.8333
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
        $payload = $this->generatePayload($resp, $respCharset);
394 536
395 536
        if ($returnPayload) {
396 536
            return $payload;
397 536
        }
398 515
399 515
        $this->printPayload($payload, $resp->getContentType(), $respEncoding);
400 515
401 452
        // return response, in case subclasses want it
402
        return $resp;
403 148
    }
404
405
    /**
406
     * Add a method to the dispatch map.
407
     *
408
     * @param string $methodName the name with which the method will be made available
409
     * @param callable $function the php function that will get invoked
410 515
     * @param array[] $sig the array of valid method signatures.
411 22
     *                     Each element is one signature: an array of strings with at least one element
412 22
     *                     First element = type of returned value. Elements 2..N = types of parameters 1..N
413 22
     * @param string $doc method documentation
414 22
     * @param array[] $sigDoc the array of valid method signatures docs, following the format of $sig but with
415 22
     *                        descriptions instead of types (one string for return type, one per param)
416
     * @param string $parametersType to allow single method handlers to receive php values instead of a Request, or vice-versa
417
     * @param int $exceptionHandling @see $this->exception_handling
418 536
     * @return void
419 536
     *
420
     * @todo raise a warning if the user tries to register a 'system.' method - but allow users to do that
421
     */
422
    public function addToMap($methodName, $function, $sig = null, $doc = false, $sigDoc = false, $parametersType = false,
423 22
        $exceptionHandling = false)
424
    {
425
       $this->add_to_map($methodName, $function, $sig, $doc, $sigDoc, $parametersType, $exceptionHandling);
0 ignored issues
show
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

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

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

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

425
       $this->add_to_map($methodName, $function, $sig, $doc, /** @scrutinizer ignore-type */ $sigDoc, $parametersType, $exceptionHandling);
Loading history...
Bug introduced by
It seems like $doc can also be of type false; however, parameter $doc of PhpXmlRpc\Server::add_to_map() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

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

839
                    array_unshift(/** @scrutinizer ignore-type */ $params, $this);
Loading history...
840 127
                    $r = call_user_func_array($func, $params);
841
                } else {
842
                    // 3rd API convention for method-handling functions: EPI-style
843
                    if ($this->functions_parameters_type == 'epivals') {
844
                        $r = call_user_func_array($func, array($methodName, $params, $this->user_data));
845
                        // mimic EPI behaviour: if we get an array that looks like an error, make it an error response
846 127
                        if (is_array($r) && array_key_exists('faultCode', $r) && array_key_exists('faultString', $r)) {
847
                            $r = new static::$responseClass(0, (int)$r['faultCode'], (string)$r['faultString']);
848
                        } else {
849 127
                            // functions using EPI api should NOT return resp objects, so make sure we encode the
850 127
                            // return type correctly
851
                            $encoder = new Encoder();
852
                            $r = new static::$responseClass($encoder->encode($r, array('extension_api')));
853
                        }
854 127
                    } else {
855 127
                        $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

855
                        $r = call_user_func_array($func, /** @scrutinizer ignore-type */ $params);
Loading history...
856 127
                    }
857
                }
858
                // the return type can be either a Response object or a plain php value...
859
                if (!is_a($r, '\PhpXmlRpc\Response')) {
860 127
                    // q: what should we assume here about automatic encoding of datetimes and php classes instances?
861 127
                    // a: let the user decide
862 127
                    $encoder = new Encoder();
863
                    $r = new static::$responseClass($encoder->encode($r, $this->phpvals_encoding_options));
864
                }
865
            }
866 127
        /// @todo bump minimum php version to 7.1 and use a single catch clause instead of the duplicate blocks
867 127
        } catch (\Exception $e) {
868 127
            // (barring errors in the lib) an uncaught exception happened in the called function, we wrap it in a
869
            // proper error-response
870
            switch ($exception_handling) {
871
                case 2:
872 127
                    if (self::$_xmlrpcs_prev_ehandler) {
873 127
                        set_error_handler(self::$_xmlrpcs_prev_ehandler);
874 127
                        self::$_xmlrpcs_prev_ehandler = null;
875
                    } else {
876
                        restore_error_handler();
877
                    }
878
                    throw $e;
879
                case 1:
880
                    $errCode = $e->getCode();
881
                    if ($errCode == 0) {
882
                        $errCode = PhpXmlRpc::$xmlrpcerr['server_error'];
883
                    }
884
                    $r = new static::$responseClass(0, $errCode, $e->getMessage());
885
                    break;
886
                default:
887
                    $r = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['server_error'], PhpXmlRpc::$xmlrpcstr['server_error']);
888
            }
889
        } catch (\Error $e) {
890
            // (barring errors in the lib) an uncaught exception happened in the called function, we wrap it in a
891
            // proper error-response
892
            switch ($exception_handling) {
893
                case 2:
894
                    if (self::$_xmlrpcs_prev_ehandler) {
895
                        set_error_handler(self::$_xmlrpcs_prev_ehandler);
896
                        self::$_xmlrpcs_prev_ehandler = null;
897
                    } else {
898
                        restore_error_handler();
899
                    }
900
                    throw $e;
901
                case 1:
902
                    $errCode = $e->getCode();
903
                    if ($errCode == 0) {
904
                        $errCode = PhpXmlRpc::$xmlrpcerr['server_error'];
905
                    }
906
                    $r = new static::$responseClass(0, $errCode, $e->getMessage());
907
                    break;
908
                default:
909
                    $r = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['server_error'], PhpXmlRpc::$xmlrpcstr['server_error']);
910
            }
911
        }
912
913
        // note: restore the error handler we found before calling the user func, even if it has been changed
914
        // inside the func itself
915
        if (self::$_xmlrpcs_prev_ehandler) {
916
            set_error_handler(self::$_xmlrpcs_prev_ehandler);
917
            self::$_xmlrpcs_prev_ehandler = null;
918
        } else {
919
            restore_error_handler();
920
        }
921
922
        return $r;
923
    }
924
925
    /**
926
     * @param Response $resp
927
     * @param string $respCharset
928
     * @return string
929
     */
930
    protected function generatePayload($resp, $respCharset)
931
    {
932
        $header = $resp->xml_header($respCharset);
933 22
        if ($this->debug > 0) {
934
            $header .= $this->serializeDebug($respCharset);
935 22
        }
936 22
937 22
        // Do not create response serialization if it has already happened. Helps to build json magic
938
        /// @todo what if the payload was created targeting a different charset than $respCharset?
939 22
        ///       Also, if we do not call serialize(), the request will not set its content-type to have the charset declared
940 22
        $payload = $resp->getPayload();
941
        if (empty($payload)) {
942
            $payload = $resp->serialize($respCharset);
943 22
        }
944
945
        return $header . $payload;
946
    }
947
948
    /**
949
     * @param string $payload
950
     * @param string $respContentType
951 106
     * @param string $respEncoding
952
     * @return void
953
     */
954 106
    protected function printPayload($payload, $respContentType, $respEncoding)
955 106
    {
956 106
        // if we get a warning/error that has output some text before here, then we cannot
957
        // add a new header. We cannot say we are sending xml, either...
958
        if (!headers_sent()) {
959
            header('Content-Type: ' . $respContentType);
960 106
            // we do not know if client actually told us an accepted charset, but if it did we have to tell it what we did
961 85
            header("Vary: Accept-Charset");
962
963 22
            // http compression of output: only if we can do it, and we want to do it, and client asked us to,
964
            // and php ini settings do not force it already
965 106
            $phpNoSelfCompress = !ini_get('zlib.output_compression') && (ini_get('output_handler') != 'ob_gzhandler');
966 106
            if ($this->compress_response && $respEncoding != '' && $phpNoSelfCompress) {
967 106
                if (strpos($respEncoding, 'gzip') !== false && function_exists('gzencode')) {
968 106
                    $payload = gzencode($payload);
969 106
                    header("Content-Encoding: gzip");
970 106
                    header("Vary: Accept-Encoding");
971 106
                } elseif (strpos($respEncoding, 'deflate') !== false && function_exists('gzcompress')) {
972
                    $payload = gzcompress($payload);
973 106
                    header("Content-Encoding: deflate");
974
                    header("Vary: Accept-Encoding");
975 106
                }
976
            }
977
978
            // Do not output content-length header if php is compressing output for us: it will mess up measurements.
979
            // Note that Apache/mod_php will add (and even alter!) the Content-Length header on its own, but only for
980
            // responses up to 8000 bytes
981
            if ($phpNoSelfCompress) {
982 1
                header('Content-Length: ' . (int)strlen($payload));
983
            }
984
        } else {
985 106
            /// @todo allow the user to easily subclass this in a way which allows the resp. headers to be already sent
986
            ///       by now without flagging it as an error. Possibly check for presence of Content-Type header
987
            $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': http headers already sent before response is fully generated. Check for php warning or error messages');
988
        }
989
990
        print $payload;
991
    }
992
993 85
    /**
994
     * Registered as callback for when the XMLParser has found the name of the method to execute.
995
     * Handling that early allows to 1. stop parsing the rest of the xml if there is no such method registered, and
996 85
     * 2. tweak the type of data that the parser will return, in case the server uses mixed-calling-convention
997 85
     *
998 85
     * @internal
999
     * @param $methodName
1000
     * @param XMLParser $xmlParser
1001
     * @param null|resource $parser
1002 85
     * @return void
1003 85
     * @throws NoSuchMethodException
1004
     *
1005 1
     * @todo feature creep - we could validate here that the method in the dispatch map is valid, but that would mean
1006
     *       dirtying a lot the logic, as we would have back to both parseRequest() and execute() methods the info
1007 85
     *       about the matched method handler, in order to avoid doing the work twice...
1008 85
     */
1009 85
    public function methodNameCallback($methodName, $xmlParser, $parser = null)
1010
    {
1011
        $sysCall = $this->isSyscall($methodName);
1012
        $dmap = $sysCall ? $this->getSystemDispatchMap() : $this->dmap;
1013
1014
        if (!isset($dmap[$methodName]['function'])) {
1015
            // No such method
1016
            throw new NoSuchMethodException(PhpXmlRpc::$xmlrpcstr['unknown_method'], PhpXmlRpc::$xmlrpcerr['unknown_method']);
1017 85
        }
1018
1019
        // alter on-the-fly the config of the xml parser if needed
1020 64
        if (isset($dmap[$methodName]['parameters_type']) &&
1021
            $dmap[$methodName]['parameters_type'] != $this->functions_parameters_type) {
1022 64
            /// @todo this should be done by a method of the XMLParser
1023 64
            switch ($dmap[$methodName]['parameters_type']) {
1024 64
                case XMLParser::RETURN_PHP:
1025
                    xml_set_element_handler($parser, array($xmlParser, 'xmlrpc_se'), array($xmlParser, 'xmlrpc_ee_fast'));
1026 64
                    break;
1027 64
                case XMLParser::RETURN_EPIVALS:
1028
                    xml_set_element_handler($parser, array($xmlParser, 'xmlrpc_se'), array($xmlParser, 'xmlrpc_ee_epi'));
1029 64
                    break;
1030 64
                /// @todo log a warning on unsupported return type
1031 64
                case XMLParser::RETURN_XMLRPCVALS:
1032
                default:
1033 64
                    xml_set_element_handler($parser, array($xmlParser, 'xmlrpc_se'), array($xmlParser, 'xmlrpc_ee'));
1034
            }
1035
        }
1036
    }
1037
1038
    /**
1039
     * Add a string to the 'internal debug message' (separate from 'user debug message').
1040
     *
1041 64
     * @param string $string
1042
     * @return void
1043 64
     */
1044
    protected function debugMsg($string)
1045
    {
1046 64
        $this->debug_info .= $string . "\n";
1047 64
    }
1048
1049
    /**
1050 64
     * @param string $methName
1051
     * @return bool
1052
     */
1053 64
    protected function isSyscall($methName)
1054 64
    {
1055
        return (strpos($methName, "system.") === 0);
1056
    }
1057 64
1058 64
    /**
1059
     * @param array $dmap
1060
     * @return $this
1061 64
     */
1062
    public function setDispatchMap($dmap)
1063
    {
1064
        $this->dmap = $dmap;
1065 64
        return $this;
1066 64
    }
1067 64
1068
    /**
1069
     * @return array[]
1070
     */
1071
    public function getDispatchMap()
1072
    {
1073
        return $this->dmap;
1074
    }
1075 64
1076
    /**
1077 64
     * @return array[]
1078 64
     */
1079
    public function getSystemDispatchMap()
1080
    {
1081 64
        if (!$this->allow_system_funcs) {
1082
            return array();
1083
        }
1084
1085
        return array(
1086
            'system.listMethods' => array(
1087
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_listMethods',
1088
                // listMethods: signature was either a string, or nothing.
1089
                // The useless string variant has been removed
1090
                'signature' => array(array(Value::$xmlrpcArray)),
1091
                'docstring' => 'This method lists all the methods that the XML-RPC server knows how to dispatch',
1092
                'signature_docs' => array(array('list of method names')),
1093
            ),
1094
            'system.methodHelp' => array(
1095
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_methodHelp',
1096
                'signature' => array(array(Value::$xmlrpcString, Value::$xmlrpcString)),
1097
                'docstring' => 'Returns help text if defined for the method passed, otherwise returns an empty string',
1098
                'signature_docs' => array(array('method description', 'name of the method to be described')),
1099
            ),
1100
            'system.methodSignature' => array(
1101
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_methodSignature',
1102
                'signature' => array(array(Value::$xmlrpcArray, Value::$xmlrpcString)),
1103
                '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)',
1104
                'signature_docs' => array(array('list of known signatures, each sig being an array of xmlrpc type names', 'name of method to be described')),
1105
            ),
1106
            'system.multicall' => array(
1107
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_multicall',
1108
                'signature' => array(array(Value::$xmlrpcArray, Value::$xmlrpcArray)),
1109
                'docstring' => 'Boxcar multiple RPC calls in one request. See http://www.xmlrpc.com/discuss/msgReader$1208 for details',
1110
                '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"')),
1111
            ),
1112
            'system.getCapabilities' => array(
1113
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_getCapabilities',
1114
                'signature' => array(array(Value::$xmlrpcStruct)),
1115
                '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',
1116
                'signature_docs' => array(array('list of capabilities, described as structs with a version number and url for the spec')),
1117
            ),
1118
        );
1119
    }
1120
1121
    /**
1122
     * @return array[]
1123
     */
1124
    public function getCapabilities()
1125
    {
1126
        $outAr = array(
1127
            // xml-rpc spec: always supported
1128
            'xmlrpc' => array(
1129
                // NB: the spec sits now at https://xmlrpc.com/spec.md
1130
                'specUrl' => 'http://www.xmlrpc.com/spec',
1131
                'specVersion' => 1
1132
            ),
1133
            // if we support system.xxx functions, we always support multicall, too...
1134
            'system.multicall' => array(
1135
                // Note that, as of 2006/09/17, the following URL does not respond anymore
1136
                'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
1137 85
                'specVersion' => 1
1138
            ),
1139 85
            // introspection: version 2! we support 'mixed', too.
1140
            // note: the php xml-rpc extension says this instead:
1141 85
            //   url http://xmlrpc-epi.sourceforge.net/specs/rfc.introspection.php, version 20010516
1142 85
            'introspection' => array(
1143 85
                'specUrl' => 'http://phpxmlrpc.sourceforge.net/doc-2/ch10.html',
1144 64
                'specVersion' => 2,
1145
            ),
1146
        );
1147
1148
        // NIL extension
1149
        if (PhpXmlRpc::$xmlrpc_null_extension) {
1150
            $outAr['nil'] = array(
1151
                // Note that, as of 2023/01, the following URL does not respond anymore
1152
                'specUrl' => 'http://www.ontosys.com/xml-rpc/extensions.php',
1153 85
                'specVersion' => 1
1154
            );
1155
        }
1156
1157
        // support for "standard" error codes
1158
        if (PhpXmlRpc::$xmlrpcerr['unknown_method'] === Interop::$xmlrpcerr['unknown_method']) {
1159
            $outAr['faults_interop'] = array(
1160
                'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
1161
                'specVersion' => 20010516
1162
            );
1163
        }
1164 43
1165
        return $outAr;
1166
    }
1167 43
1168 22
    /**
1169
     * @internal handler of a system. method
1170
     *
1171
     * @param Server $server
1172 22
     * @param Request $req
1173 22
     * @return Response
1174
     */
1175
    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

1175
    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...
1176
    {
1177 22
        $encoder = new Encoder();
1178
        return new static::$responseClass($encoder->encode($server->getCapabilities()));
1179
    }
1180 22
1181 22
    /**
1182 22
     * @internal handler of a system. method
1183
     *
1184 22
     * @param Server $server
1185
     * @param Request $req if called in plain php values mode, second param is missing
1186
     * @return Response
1187
     */
1188
    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

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