Passed
Push — master ( 526b0d...b337d2 )
by Gaetano
04:53
created

Server::setDebug()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
ccs 2
cts 2
cp 1
crap 1
1
<?php
2
3
namespace PhpXmlRpc;
4
5
use PhpXmlRpc\Helper\Charset;
6
use PhpXmlRpc\Helper\Logger;
7
use PhpXmlRpc\Helper\XMLParser;
8
9
/**
10
 * Allows effortless implementation of XML-RPC servers
11
 */
12
class Server
13
{
14
    protected static $logger;
15
    protected static $parser;
16
    protected static $charsetEncoder;
17
18
    /**
19
     * Defines how functions in dmap will be invoked: either using an xmlrpc request object
20
     * or plain php values.
21
     * Valid strings are 'xmlrpcvals', 'phpvals' or 'epivals'
22
     * @todo create class constants for these
23
     */
24
    public $functions_parameters_type = 'xmlrpcvals';
25
26
    /**
27
     * Option used for fine-tuning the encoding the php values returned from
28
     * functions registered in the dispatch map when the functions_parameters_types
29
     * member is set to 'phpvals'
30
     * @see Encoder::encode for a list of values
31
     */
32
    public $phpvals_encoding_options = array('auto_dates');
33
34
    /**
35
     * Controls whether the server is going to echo debugging messages back to the client as comments in response body.
36
     * Valid values: 0,1,2,3
37
     */
38
    public $debug = 1;
39
40
    /**
41
     * Controls behaviour of server when the invoked user function throws an exception:
42
     * 0 = catch it and return an 'internal error' xmlrpc response (default)
43
     * 1 = catch it and return an xmlrpc response with the error corresponding to the exception
44
     * 2 = allow the exception to float to the upper layers
45
     */
46
    public $exception_handling = 0;
47
48
    /**
49
     * When set to true, it will enable HTTP compression of the response, in case
50
     * the client has declared its support for compression in the request.
51
     * Set at constructor time.
52
     */
53
    public $compress_response = false;
54
55
    /**
56
     * List of http compression methods accepted by the server for requests. Set at constructor time.
57
     * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib
58
     */
59
    public $accepted_compression = array();
60
61
    /// Shall we serve calls to system.* methods?
62
    public $allow_system_funcs = true;
63
64
    /**
65
     * List of charset encodings natively accepted for requests.
66
     * Set at constructor time.
67
     * UNUSED so far...
68
     */
69
    public $accepted_charset_encodings = array();
70
71
    /**
72
     * Charset encoding to be used for response.
73
     * NB: if we can, we will convert the generated response from internal_encoding to the intended one.
74
     * Can be: a supported xml encoding (only UTF-8 and ISO-8859-1 at present, unless mbstring is enabled),
75
     * null (leave unspecified in response, convert output stream to US_ASCII),
76
     * 'default' (use xmlrpc library default as specified in xmlrpc.inc, convert output stream if needed),
77
     * or '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).
78
     * NB: pretty dangerous if you accept every charset and do not have mbstring enabled)
79
     */
80
    public $response_charset_encoding = '';
81
82
    /**
83
     * Extra data passed at runtime to method handling functions. Used only by EPI layer
84
     */
85
    public $user_data = null;
86
87
    /**
88
     * Array defining php functions exposed as xmlrpc methods by this server.
89
     * @var array[] $dmap
90
     */
91
    protected $dmap = array();
92
93
    /**
94
     * Storage for internal debug info.
95
     */
96
    protected $debug_info = '';
97
98
    protected static $_xmlrpc_debuginfo = '';
99
    protected static $_xmlrpcs_occurred_errors = '';
100
    protected static $_xmlrpcs_prev_ehandler = '';
101
102
    public function getLogger()
103
    {
104
        if (self::$logger === null) {
105
            self::$logger = Logger::instance();
106
        }
107
        return self::$logger;
108
    }
109
110
    public static function setLogger($logger)
111
    {
112
        self::$logger = $logger;
113
    }
114
115 491
    public function getParser()
116
    {
117 491
        if (self::$parser === null) {
118 491
            self::$parser = new XMLParser();
119
        }
120 491
        return self::$parser;
121
    }
122
123
    public static function setParser($parser)
124
    {
125
        self::$parser = $parser;
126
    }
127
128 2
    public function getCharsetEncoder()
129
    {
130 2
        if (self::$charsetEncoder === null) {
131 2
            self::$charsetEncoder = Charset::instance();
132
        }
133 2
        return self::$charsetEncoder;
134
    }
135
136
    public function setCharsetEncoder($charsetEncoder)
137
    {
138
        self::$charsetEncoder = $charsetEncoder;
139
    }
140
141
    /**
142
     * @param array[] $dispatchMap the dispatch map with definition of exposed services
143
     *                             Array keys are the names of the method names.
144
     *                             Each array value is an array with the following members:
145
     *                             - function (callable)
146
     *                             - docstring (optional)
147
     *                             - signature (array, optional)
148
     *                             - signature_docs (array, optional)
149
     *                             - parameters_type (string, optional)
150
     * @param boolean $serviceNow set to false to prevent the server from running upon construction
151
     */
152 491
    public function __construct($dispatchMap = null, $serviceNow = true)
153
    {
154
        // if ZLIB is enabled, let the server by default accept compressed requests,
155
        // and compress responses sent to clients that support them
156 491
        if (function_exists('gzinflate')) {
157 491
            $this->accepted_compression = array('gzip', 'deflate');
158 491
            $this->compress_response = true;
159
        }
160
161
        // by default the xml parser can support these 3 charset encodings
162 491
        $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII');
163
164
        // dispMap is a dispatch array of methods mapped to function names and signatures.
165
        // If a method doesn't appear in the map then an unknown method error is generated
166
        /* milosch - changed to make passing dispMap optional.
167
        * instead, you can use the class add_to_map() function
168
        * to add functions manually (borrowed from SOAPX4)
169
        */
170 491
        if ($dispatchMap) {
171 490
            $this->dmap = $dispatchMap;
172 490
            if ($serviceNow) {
173 2
                $this->service();
174
            }
175
        }
176 491
    }
177
178
    /**
179
     * Set debug level of server.
180
     *
181
     * @param integer $level debug lvl: determines info added to xmlrpc responses (as xml comments)
182
     *                    0 = no debug info,
183
     *                    1 = msgs set from user with debugmsg(),
184
     *                    2 = add complete xmlrpc request (headers and body),
185
     *                    3 = add also all processing warnings happened during method processing
186
     *                    (NB: this involves setting a custom error handler, and might interfere
187
     *                    with the standard processing of the php function exposed as method. In
188
     *                    particular, triggering an USER_ERROR level error will not halt script
189
     *                    execution anymore, but just end up logged in the xmlrpc response)
190
     *                    Note that info added at level 2 and 3 will be base64 encoded
191
     */
192 488
    public function setDebug($level)
193
    {
194 488
        $this->debug = $level;
195 488
    }
196
197
    /**
198
     * Add a string to the debug info that can be later serialized by the server as part of the response message.
199
     * Note that for best compatibility, the debug string should be encoded using the PhpXmlRpc::$xmlrpc_internalencoding
200
     * character set.
201
     *
202
     * @param string $msg
203
     */
204 2
    public static function xmlrpc_debugmsg($msg)
205
    {
206 2
        static::$_xmlrpc_debuginfo .= $msg . "\n";
207 2
    }
208
209
    /**
210
     * Add a string to the debug info that will be later serialized by the server as part of the response message
211
     * (base64 encoded, only when debug level >= 2)
212
     *
213
     * character set.
214
     * @param string $msg
215
     */
216 20
    public static function error_occurred($msg)
217
    {
218 20
        static::$_xmlrpcs_occurred_errors .= $msg . "\n";
219 20
    }
220
221
    /**
222
     * Return a string with the serialized representation of all debug info.
223
     *
224
     * @param string $charsetEncoding the target charset encoding for the serialization
225
     *
226
     * @return string an XML comment (or two)
227
     */
228 490
    public function serializeDebug($charsetEncoding = '')
229
    {
230
        // Tough encoding problem: which internal charset should we assume for debug info?
231
        // It might contain a copy of raw data received from client, ie with unknown encoding,
232
        // intermixed with php generated data and user generated data...
233
        // so we split it: system debug is base 64 encoded,
234
        // user debug info should be encoded by the end user using the INTERNAL_ENCODING
235 490
        $out = '';
236 490
        if ($this->debug_info != '') {
237 488
            $out .= "<!-- SERVER DEBUG INFO (BASE64 ENCODED):\n" . base64_encode($this->debug_info) . "\n-->\n";
238
        }
239 490
        if (static::$_xmlrpc_debuginfo != '') {
240 2
            $out .= "<!-- DEBUG INFO:\n" . $this->getCharsetEncoder()->encodeEntities(str_replace('--', '_-', static::$_xmlrpc_debuginfo), PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "\n-->\n";
241
            // NB: a better solution MIGHT be to use CDATA, but we need to insert it
242
            // into return payload AFTER the beginning tag
243
            //$out .= "<![CDATA[ DEBUG INFO:\n\n" . str_replace(']]>', ']_]_>', static::$_xmlrpc_debuginfo) . "\n]]>\n";
244
        }
245
246 490
        return $out;
247
    }
248
249
    /**
250
     * Execute the xmlrpc request, printing the response.
251
     *
252
     * @param string $data the request body. If null, the http POST request will be examined
253
     * @param bool $returnPayload When true, return the response but do not echo it or any http header
254
     *
255
     * @return Response|string the response object (usually not used by caller...) or its xml serialization
256
     *
257
     * @throws \Exception in case the executed method does throw an exception (and depending on server configuration)
258
     */
259 490
    public function service($data = null, $returnPayload = false)
260
    {
261 490
        if ($data === null) {
262 490
            $data = file_get_contents('php://input');
263
        }
264 490
        $rawData = $data;
265
266
        // reset internal debug info
267 490
        $this->debug_info = '';
268
269
        // Save what we received, before parsing it
270 490
        if ($this->debug > 1) {
271 488
            $this->debugmsg("+++GOT+++\n" . $data . "\n+++END+++");
272
        }
273
274 490
        $r = $this->parseRequestHeaders($data, $reqCharset, $respCharset, $respEncoding);
275 490
        if (!$r) {
276
            // this actually executes the request
277 490
            $r = $this->parseRequest($data, $reqCharset);
278
        }
279
280
        // save full body of request into response, for more debugging usages
281 490
        $r->raw_data = $rawData;
282
283 490
        if ($this->debug > 2 && static::$_xmlrpcs_occurred_errors) {
284 20
            $this->debugmsg("+++PROCESSING ERRORS AND WARNINGS+++\n" .
285 20
                static::$_xmlrpcs_occurred_errors . "+++END+++");
286
        }
287
288 490
        $payload = $this->xml_header($respCharset);
289 490
        if ($this->debug > 0) {
290 490
            $payload = $payload . $this->serializeDebug($respCharset);
291
        }
292
293
        // Do not create response serialization if it has already happened. Helps building json magic
294 490
        if (empty($r->payload)) {
295 490
            $r->serialize($respCharset);
296
        }
297 490
        $payload = $payload . $r->payload;
298
299 490
        if ($returnPayload) {
300
            return $payload;
301
        }
302
303
        // if we get a warning/error that has output some text before here, then we cannot
304
        // add a new header. We cannot say we are sending xml, either...
305 490
        if (!headers_sent()) {
306 490
            header('Content-Type: ' . $r->content_type);
307
            // we do not know if client actually told us an accepted charset, but if he did
308
            // we have to tell him what we did
309 490
            header("Vary: Accept-Charset");
310
311
            // http compression of output: only
312
            // if we can do it, and we want to do it, and client asked us to,
313
            // and php ini settings do not force it already
314
            /// @todo check separately for gzencode and gzcompress functions, in case of polyfills
315 490
            $phpNoSelfCompress = !ini_get('zlib.output_compression') && (ini_get('output_handler') != 'ob_gzhandler');
316 490
            if ($this->compress_response && function_exists('gzencode') && $respEncoding != ''
317 100
                && $phpNoSelfCompress
318
            ) {
319 100
                if (strpos($respEncoding, 'gzip') !== false) {
320 50
                    $payload = gzencode($payload);
321 50
                    header("Content-Encoding: gzip");
322 50
                    header("Vary: Accept-Encoding");
323 50
                } elseif (strpos($respEncoding, 'deflate') !== false) {
324 50
                    $payload = gzcompress($payload);
325 50
                    header("Content-Encoding: deflate");
326 50
                    header("Vary: Accept-Encoding");
327
                }
328
            }
329
330
            // Do not output content-length header if php is compressing output for us:
331
            // it will mess up measurements.
332
            // Note that Apache/mod_php will add (and even alter!) the Content-Length header on its own, but only for
333
            // responses up to 8000 bytes
334 490
            if ($phpNoSelfCompress) {
335 490
                header('Content-Length: ' . (int)strlen($payload));
336
            }
337
        } else {
338
            $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': http headers already sent before response is fully generated. Check for php warning or error messages');
339
        }
340
341 490
        print $payload;
342
343
        // return request, in case subclasses want it
344 490
        return $r;
345
    }
346
347
    /**
348
     * Add a method to the dispatch map.
349
     *
350
     * @param string $methodName the name with which the method will be made available
351
     * @param callable $function the php function that will get invoked
352
     * @param array[] $sig the array of valid method signatures.
353
     *                     Each element is one signature: an array of strings with at least one element
354
     *                     First element = type of returned value. Elements 2..N = types of parameters 1..N
355
     * @param string $doc method documentation
356
     * @param array[] $sigDoc the array of valid method signatures docs, following the format of $sig but with
357
     *                        descriptions instead of types (one string for return type, one per param)
358
     *
359
     * @todo raise a warning if the user tries to register a 'system.' method
360
     * @todo allow setting parameters_type
361
     */
362
    public function add_to_map($methodName, $function, $sig = null, $doc = false, $sigDoc = false)
363
    {
364
        $this->dmap[$methodName] = array(
365
            'function' => $function,
366
            'docstring' => $doc,
367
        );
368
        if ($sig) {
369
            $this->dmap[$methodName]['signature'] = $sig;
370
        }
371
        if ($sigDoc) {
372
            $this->dmap[$methodName]['signature_docs'] = $sigDoc;
373
        }
374
    }
375
376
    /**
377
     * Verify type and number of parameters received against a list of known signatures.
378
     *
379
     * @param array|Request $in array of either xmlrpc value objects or xmlrpc type definitions
380
     * @param array $sigs array of known signatures to match against
381
     *
382
     * @return array int, string
383
     */
384 467
    protected function verifySignature($in, $sigs)
385
    {
386
        // check each possible signature in turn
387 467
        if (is_object($in)) {
388 467
            $numParams = $in->getNumParams();
389
        } else {
390
            $numParams = count($in);
391
        }
392 467
        foreach ($sigs as $curSig) {
393 467
            if (count($curSig) == $numParams + 1) {
394 467
                $itsOK = 1;
395 467
                for ($n = 0; $n < $numParams; $n++) {
396 448
                    if (is_object($in)) {
397 448
                        $p = $in->getParam($n);
398 448
                        if ($p->kindOf() == 'scalar') {
399 391
                            $pt = $p->scalartyp();
400
                        } else {
401 134
                            $pt = $p->kindOf();
402
                        }
403
                    } else {
404
                        $pt = ($in[$n] == 'i4') ? 'int' : strtolower($in[$n]); // dispatch maps never use i4...
405
                    }
406
407
                    // param index is $n+1, as first member of sig is return type
408 448
                    if ($pt != $curSig[$n + 1] && $curSig[$n + 1] != Value::$xmlrpcValue) {
409 20
                        $itsOK = 0;
410 20
                        $pno = $n + 1;
411 20
                        $wanted = $curSig[$n + 1];
412 20
                        $got = $pt;
413 20
                        break;
414
                    }
415
                }
416 467
                if ($itsOK) {
417 467
                    return array(1, '');
418
                }
419
            }
420
        }
421 20
        if (isset($wanted)) {
422
            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...
423
        } else {
424 20
            return array(0, "No method signature matches number of parameters");
425
        }
426
    }
427
428
    /**
429
     * Parse http headers received along with xmlrpc request. If needed, inflate request.
430
     *
431
     * @return mixed Response|null on success or an error Response
432
     */
433 490
    protected function parseRequestHeaders(&$data, &$reqEncoding, &$respEncoding, &$respCompression)
434
    {
435
        // check if $_SERVER is populated: it might have been disabled via ini file
436
        // (this is true even when in CLI mode)
437 490
        if (count($_SERVER) == 0) {
438
            $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': cannot parse request headers as $_SERVER is not populated');
439
        }
440
441 490
        if ($this->debug > 1) {
442 488
            if (function_exists('getallheaders')) {
443 488
                $this->debugmsg(''); // empty line
444 488
                foreach (getallheaders() as $name => $val) {
445 488
                    $this->debugmsg("HEADER: $name: $val");
446
                }
447
            }
448
        }
449
450 490
        if (isset($_SERVER['HTTP_CONTENT_ENCODING'])) {
451 100
            $contentEncoding = str_replace('x-', '', $_SERVER['HTTP_CONTENT_ENCODING']);
452
        } else {
453 390
            $contentEncoding = '';
454
        }
455
456
        // check if request body has been compressed and decompress it
457 490
        if ($contentEncoding != '' && strlen($data)) {
458 100
            if ($contentEncoding == 'deflate' || $contentEncoding == 'gzip') {
459
                // if decoding works, use it. else assume data wasn't gzencoded
460 100
                if (function_exists('gzinflate') && in_array($contentEncoding, $this->accepted_compression)) {
461 100
                    if ($contentEncoding == 'deflate' && $degzdata = @gzuncompress($data)) {
462 50
                        $data = $degzdata;
463 50
                        if ($this->debug > 1) {
464 50
                            $this->debugmsg("\n+++INFLATED REQUEST+++[" . strlen($data) . " chars]+++\n" . $data . "\n+++END+++");
465
                        }
466 50
                    } elseif ($contentEncoding == 'gzip' && $degzdata = @gzinflate(substr($data, 10))) {
467 50
                        $data = $degzdata;
468 50
                        if ($this->debug > 1) {
469 50
                            $this->debugmsg("+++INFLATED REQUEST+++[" . strlen($data) . " chars]+++\n" . $data . "\n+++END+++");
470
                        }
471
                    } else {
472
                        $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_decompress_fail'], PhpXmlRpc::$xmlrpcstr['server_decompress_fail']);
473
474
                        return $r;
475
                    }
476
                } else {
477
                    $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_cannot_decompress'], PhpXmlRpc::$xmlrpcstr['server_cannot_decompress']);
478
479
                    return $r;
480
                }
481
            }
482
        }
483
484
        // check if client specified accepted charsets, and if we know how to fulfill
485
        // the request
486 490
        if ($this->response_charset_encoding == 'auto') {
487
            $respEncoding = '';
488
            if (isset($_SERVER['HTTP_ACCEPT_CHARSET'])) {
489
                // here we should check if we can match the client-requested encoding
490
                // with the encodings we know we can generate.
491
                /// @todo we should parse q=0.x preferences instead of getting first charset specified...
492
                $clientAcceptedCharsets = explode(',', strtoupper($_SERVER['HTTP_ACCEPT_CHARSET']));
493
                // Give preference to internal encoding
494
                $knownCharsets = array(PhpXmlRpc::$xmlrpc_internalencoding, 'UTF-8', 'ISO-8859-1', 'US-ASCII');
495
                foreach ($knownCharsets as $charset) {
496
                    foreach ($clientAcceptedCharsets as $accepted) {
497
                        if (strpos($accepted, $charset) === 0) {
498
                            $respEncoding = $charset;
499
                            break;
500
                        }
501
                    }
502
                    if ($respEncoding) {
503
                        break;
504
                    }
505
                }
506
            }
507
        } else {
508 490
            $respEncoding = $this->response_charset_encoding;
509
        }
510
511 490
        if (isset($_SERVER['HTTP_ACCEPT_ENCODING'])) {
512 100
            $respCompression = $_SERVER['HTTP_ACCEPT_ENCODING'];
513
        } else {
514 390
            $respCompression = '';
515
        }
516
517
        // 'guestimate' request encoding
518
        /// @todo check if mbstring is enabled and automagic input conversion is on: it might mingle with this check???
519 490
        $reqEncoding = XMLParser::guessEncoding(isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : '',
520
            $data);
521
522 490
        return null;
523
    }
524
525
    /**
526
     * Parse an xml chunk containing an xmlrpc request and execute the corresponding
527
     * php function registered with the server.
528
     *
529
     * @param string $data the xml request
530
     * @param string $reqEncoding (optional) the charset encoding of the xml request
531
     *
532
     * @return Response
533
     *
534
     * @throws \Exception in case the executed method does throw an exception (and depending on server configuration)
535
     *
536
     * @internal this function will become protected in the future
537
     * @todo either rename this function or move the 'execute' part out of it...
538
     */
539 491
    public function parseRequest($data, $reqEncoding = '')
540
    {
541
        // decompose incoming XML into request structure
542
543 491
        if ($reqEncoding != '') {
544
            // Since parsing will fail if
545
            // - charset is not specified in the xml prologue,
546
            // - the encoding is not UTF8 and
547
            // - there are non-ascii chars in the text,
548
            // we try to work round that...
549
            // The following code might be better for mb_string enabled installs, but
550
            // makes the lib about 200% slower...
551
            //if (!is_valid_charset($reqEncoding, array('UTF-8')))
552 490
            if (!in_array($reqEncoding, array('UTF-8', 'US-ASCII')) && !XMLParser::hasEncoding($data)) {
553 4
                if ($reqEncoding == 'ISO-8859-1') {
554 2
                    $data = utf8_encode($data);
555
                } else {
556 2
                    if (extension_loaded('mbstring')) {
557 2
                        $data = mb_convert_encoding($data, 'UTF-8', $reqEncoding);
558
                    } else {
559
                        $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': invalid charset encoding of received request: ' . $reqEncoding);
560
                    }
561
                }
562
            }
563
        }
564
565
        // PHP internally might use ISO-8859-1, so we have to tell the xml parser to give us back data in the expected charset.
566
        // What if internal encoding is not in one of the 3 allowed? We use the broadest one, ie. utf8
567
        // This allows to send data which is native in various charset,
568
        // by extending xmlrpc_encode_entities() and setting xmlrpc_internalencoding
569 491
        if (!in_array(PhpXmlRpc::$xmlrpc_internalencoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) {
570
            /// @todo emit a warning
571
            $options = array(XML_OPTION_TARGET_ENCODING => 'UTF-8');
572
        } else {
573 491
            $options = array(XML_OPTION_TARGET_ENCODING => PhpXmlRpc::$xmlrpc_internalencoding);
574
        }
575
576 491
        $xmlRpcParser = $this->getParser();
577 491
        $xmlRpcParser->parse($data, $this->functions_parameters_type, XMLParser::ACCEPT_REQUEST, $options);
578 491
        if ($xmlRpcParser->_xh['isf'] > 2) {
579
            // (BC) we return XML error as a faultCode
580 2
            preg_match('/^XML error ([0-9]+)/', $xmlRpcParser->_xh['isf_reason'], $matches);
581 2
            $r = new Response(0,
582 2
                PhpXmlRpc::$xmlrpcerrxml + $matches[1],
583 2
                $xmlRpcParser->_xh['isf_reason']);
584 489
        } elseif ($xmlRpcParser->_xh['isf']) {
585 1
            $r = new Response(0,
586 1
                PhpXmlRpc::$xmlrpcerr['invalid_request'],
587 1
                PhpXmlRpc::$xmlrpcstr['invalid_request'] . ' ' . $xmlRpcParser->_xh['isf_reason']);
588
        } else {
589
            // small layering violation in favor of speed and memory usage:
590
            // we should allow the 'execute' method handle this, but in the
591
            // most common scenario (xmlrpc values type server with some methods
592
            // registered as phpvals) that would mean a useless encode+decode pass
593 488
            if ($this->functions_parameters_type != 'xmlrpcvals' ||
594 488
                (isset($this->dmap[$xmlRpcParser->_xh['method']]['parameters_type']) &&
595
                    ($this->dmap[$xmlRpcParser->_xh['method']]['parameters_type'] != 'xmlrpcvals')
596
                )
597
            ) {
598
                if ($this->debug > 1) {
599
                    $this->debugmsg("\n+++PARSED+++\n" . var_export($xmlRpcParser->_xh['params'], true) . "\n+++END+++");
600
                }
601
                $r = $this->execute($xmlRpcParser->_xh['method'], $xmlRpcParser->_xh['params'], $xmlRpcParser->_xh['pt']);
602
            } else {
603
                // build a Request object with data parsed from xml
604 488
                $req = new Request($xmlRpcParser->_xh['method']);
605
                // now add parameters in
606 488
                for ($i = 0; $i < count($xmlRpcParser->_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...
607 469
                    $req->addParam($xmlRpcParser->_xh['params'][$i]);
608
                }
609
610 488
                if ($this->debug > 1) {
611 488
                    $this->debugmsg("\n+++PARSED+++\n" . var_export($req, true) . "\n+++END+++");
612
                }
613 488
                $r = $this->execute($req);
614
            }
615
        }
616
617 491
        return $r;
618
    }
619
620
    /**
621
     * Execute a method invoked by the client, checking parameters used.
622
     *
623
     * @param Request|string $req either a Request obj or a method name
624
     * @param mixed[] $params array with method parameters as php types (only if m is method name)
625
     * @param string[] $paramTypes array with xmlrpc types of method parameters (only if m is method name)
626
     *
627
     * @return Response
628
     *
629
     * @throws \Exception in case the executed method does throw an exception (and depending on server configuration)
630
     */
631 488
    protected function execute($req, $params = null, $paramTypes = null)
632
    {
633 488
        static::$_xmlrpcs_occurred_errors = '';
634 488
        static::$_xmlrpc_debuginfo = '';
635
636 488
        if (is_object($req)) {
637 488
            $methName = $req->method();
638
        } else {
639
            $methName = $req;
640
        }
641 488
        $sysCall = $this->isSyscall($methName);
642 488
        $dmap = $sysCall ? $this->getSystemDispatchMap() : $this->dmap;
643
644 488
        if (!isset($dmap[$methName]['function'])) {
645
            // No such method
646 77
            return new Response(0,
647 77
                PhpXmlRpc::$xmlrpcerr['unknown_method'],
648 77
                PhpXmlRpc::$xmlrpcstr['unknown_method']);
649
        }
650
651
        // Check signature
652 488
        if (isset($dmap[$methName]['signature'])) {
653 467
            $sig = $dmap[$methName]['signature'];
654 467
            if (is_object($req)) {
655 467
                list($ok, $errStr) = $this->verifySignature($req, $sig);
656
            } else {
657
                list($ok, $errStr) = $this->verifySignature($paramTypes, $sig);
658
            }
659 467
            if (!$ok) {
660
                // Didn't match.
661 20
                return new Response(
662 20
                    0,
663 20
                    PhpXmlRpc::$xmlrpcerr['incorrect_params'],
664 20
                    PhpXmlRpc::$xmlrpcstr['incorrect_params'] . ": ${errStr}"
665
                );
666
            }
667
        }
668
669 488
        $func = $dmap[$methName]['function'];
670
        // let the 'class::function' syntax be accepted in dispatch maps
671 488
        if (is_string($func) && strpos($func, '::')) {
672 115
            $func = explode('::', $func);
673
        }
674
675 488
        if (is_array($func)) {
676 136
            if (is_object($func[0])) {
677 22
                $funcName = get_class($func[0]) . '->' . $func[1];
678
            } else {
679 115
                $funcName = implode('::', $func);
680
            }
681 372
        } else if ($func instanceof \Closure) {
682 98
            $funcName = 'Closure';
683
        } else {
684 294
            $funcName = $func;
685
        }
686
687
        // verify that function to be invoked is in fact callable
688 488
        if (!is_callable($func)) {
689
            $this->getLogger()->errorLog("XML-RPC: " . __METHOD__ . ": function '$funcName' registered as method handler is not callable");
690
            return new Response(
691
                0,
692
                PhpXmlRpc::$xmlrpcerr['server_error'],
693
                PhpXmlRpc::$xmlrpcstr['server_error'] . ": no function matches method"
694
            );
695
        }
696
697
        // If debug level is 3, we should catch all errors generated during
698
        // processing of user function, and log them as part of response
699 488
        if ($this->debug > 2) {
700 488
            self::$_xmlrpcs_prev_ehandler = set_error_handler(array('\PhpXmlRpc\Server', '_xmlrpcs_errorHandler'));
701
        }
702
703
        try {
704
            // Allow mixed-convention servers
705 488
            if (is_object($req)) {
706 488
                if ($sysCall) {
707 115
                    $r = call_user_func($func, $this, $req);
708
                } else {
709 393
                    $r = call_user_func($func, $req);
710
                }
711 486
                if (!is_a($r, 'PhpXmlRpc\Response')) {
712
                    $this->getLogger()->errorLog("XML-RPC: " . __METHOD__ . ": function '$funcName' registered as method handler does not return an xmlrpc response object but a " . gettype($r));
713
                    if (is_a($r, 'PhpXmlRpc\Value')) {
714
                        $r = new Response($r);
715
                    } else {
716
                        $r = new Response(
717
                            0,
718
                            PhpXmlRpc::$xmlrpcerr['server_error'],
719
                            PhpXmlRpc::$xmlrpcstr['server_error'] . ": function does not return xmlrpc response object"
720
                        );
721
                    }
722
                }
723
            } else {
724
                // call a 'plain php' function
725
                if ($sysCall) {
726
                    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

726
                    array_unshift(/** @scrutinizer ignore-type */ $params, $this);
Loading history...
727
                    $r = call_user_func_array($func, $params);
728
                } else {
729
                    // 3rd API convention for method-handling functions: EPI-style
730
                    if ($this->functions_parameters_type == 'epivals') {
731
                        $r = call_user_func_array($func, array($methName, $params, $this->user_data));
732
                        // mimic EPI behaviour: if we get an array that looks like an error, make it
733
                        // an error response
734
                        if (is_array($r) && array_key_exists('faultCode', $r) && array_key_exists('faultString', $r)) {
735
                            $r = new Response(0, (integer)$r['faultCode'], (string)$r['faultString']);
736
                        } else {
737
                            // functions using EPI api should NOT return resp objects,
738
                            // so make sure we encode the return type correctly
739
                            $encoder = new Encoder();
740
                            $r = new Response($encoder->encode($r, array('extension_api')));
741
                        }
742
                    } else {
743
                        $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

743
                        $r = call_user_func_array($func, /** @scrutinizer ignore-type */ $params);
Loading history...
744
                    }
745
                }
746
                // the return type can be either a Response object or a plain php value...
747
                if (!is_a($r, '\PhpXmlRpc\Response')) {
748
                    // what should we assume here about automatic encoding of datetimes
749
                    // and php classes instances???
750
                    $encoder = new Encoder();
751
                    $r = new Response($encoder->encode($r, $this->phpvals_encoding_options));
752
                }
753
            }
754 41
        } catch (\Exception $e) {
755
            // (barring errors in the lib) an uncatched exception happened
756
            // in the called function, we wrap it in a proper error-response
757 41
            switch ($this->exception_handling) {
758 41
                case 2:
759
                    if ($this->debug > 2) {
760
                        if (self::$_xmlrpcs_prev_ehandler) {
761
                            set_error_handler(self::$_xmlrpcs_prev_ehandler);
762
                        } else {
763
                            restore_error_handler();
764
                        }
765
                    }
766
                    throw $e;
767 41
                case 1:
768 2
                    $r = new Response(0, $e->getCode(), $e->getMessage());
769 2
                    break;
770
                default:
771 41
                    $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_error'], PhpXmlRpc::$xmlrpcstr['server_error']);
772
            }
773
        }
774 488
        if ($this->debug > 2) {
775
            // note: restore the error handler we found before calling the
776
            // user func, even if it has been changed inside the func itself
777 488
            if (self::$_xmlrpcs_prev_ehandler) {
778 58
                set_error_handler(self::$_xmlrpcs_prev_ehandler);
779
            } else {
780 431
                restore_error_handler();
781
            }
782
        }
783
784 488
        return $r;
785
    }
786
787
    /**
788
     * Add a string to the 'internal debug message' (separate from 'user debug message').
789
     *
790
     * @param string $string
791
     */
792 488
    protected function debugmsg($string)
793
    {
794 488
        $this->debug_info .= $string . "\n";
795 488
    }
796
797
    /**
798
     * @param string $charsetEncoding
799
     * @return string
800
     */
801 490
    protected function xml_header($charsetEncoding = '')
802
    {
803 490
        if ($charsetEncoding != '') {
804 50
            return "<?xml version=\"1.0\" encoding=\"$charsetEncoding\"?" . ">\n";
805
        } else {
806 440
            return "<?xml version=\"1.0\"?" . ">\n";
807
        }
808
    }
809
810
    /**
811
     * @param string $methName
812
     * @return bool
813
     */
814 488
    protected function isSyscall($methName)
815
    {
816 488
        return (strpos($methName, "system.") === 0);
817
    }
818
819
    /**
820
     * @return array[]
821
     */
822
    public function getDispatchMap()
823
    {
824
        return $this->dmap;
825
    }
826
827
    /**
828
     * @return array[]
829
     */
830 115
    public function getSystemDispatchMap()
831
    {
832 115
        if (!$this->allow_system_funcs) {
833
            return array();
834
        }
835
836
        return array(
837
            'system.listMethods' => array(
838 115
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_listMethods',
839
                // listMethods: signature was either a string, or nothing.
840
                // The useless string variant has been removed
841 115
                'signature' => array(array(Value::$xmlrpcArray)),
842 115
                'docstring' => 'This method lists all the methods that the XML-RPC server knows how to dispatch',
843
                'signature_docs' => array(array('list of method names')),
844
            ),
845
            'system.methodHelp' => array(
846 115
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_methodHelp',
847 115
                'signature' => array(array(Value::$xmlrpcString, Value::$xmlrpcString)),
848 115
                'docstring' => 'Returns help text if defined for the method passed, otherwise returns an empty string',
849
                'signature_docs' => array(array('method description', 'name of the method to be described')),
850
            ),
851
            'system.methodSignature' => array(
852 115
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_methodSignature',
853 115
                'signature' => array(array(Value::$xmlrpcArray, Value::$xmlrpcString)),
854 115
                '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)',
855
                'signature_docs' => array(array('list of known signatures, each sig being an array of xmlrpc type names', 'name of method to be described')),
856
            ),
857
            'system.multicall' => array(
858 115
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_multicall',
859 115
                'signature' => array(array(Value::$xmlrpcArray, Value::$xmlrpcArray)),
860 115
                'docstring' => 'Boxcar multiple RPC calls in one request. See http://www.xmlrpc.com/discuss/msgReader$1208 for details',
861
                '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"')),
862
            ),
863
            'system.getCapabilities' => array(
864 115
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_getCapabilities',
865 115
                'signature' => array(array(Value::$xmlrpcStruct)),
866 115
                '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',
867
                'signature_docs' => array(array('list of capabilities, described as structs with a version number and url for the spec')),
868
            ),
869
        );
870
    }
871
872
    /* Functions that implement system.XXX methods of xmlrpc servers */
873
874
    /**
875
     * @return array[]
876
     */
877
    public function getCapabilities()
878
    {
879
        $outAr = array(
880
            // xmlrpc spec: always supported
881
            'xmlrpc' => array(
882
                'specUrl' => 'http://www.xmlrpc.com/spec',
883
                'specVersion' => 1
884
            ),
885
            // if we support system.xxx functions, we always support multicall, too...
886
            // Note that, as of 2006/09/17, the following URL does not respond anymore
887
            'system.multicall' => array(
888
                'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
889
                'specVersion' => 1
890
            ),
891
            // introspection: version 2! we support 'mixed', too
892
            'introspection' => array(
893
                'specUrl' => 'http://phpxmlrpc.sourceforge.net/doc-2/ch10.html',
894
                'specVersion' => 2,
895
            ),
896
        );
897
898
        // NIL extension
899
        if (PhpXmlRpc::$xmlrpc_null_extension) {
900
            $outAr['nil'] = array(
901
                'specUrl' => 'http://www.ontosys.com/xml-rpc/extensions.php',
902
                'specVersion' => 1
903
            );
904
        }
905
906
        return $outAr;
907
    }
908
909
    /**
910
     * @param Server $server
911
     * @param Request $req
912
     * @return Response
913
     */
914
    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

914
    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...
915
    {
916
        $encoder = new Encoder();
917
        return new Response($encoder->encode($server->getCapabilities()));
918
    }
919
920
    /**
921
     * @param Server $server
922
     * @param Request $req if called in plain php values mode, second param is missing
923
     * @return Response
924
     */
925 20
    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

925
    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...
926
    {
927 20
        $outAr = array();
928 20
        foreach ($server->dmap as $key => $val) {
929 20
            $outAr[] = new Value($key, 'string');
930
        }
931 20
        foreach ($server->getSystemDispatchMap() as $key => $val) {
932 20
            $outAr[] = new Value($key, 'string');
933
        }
934
935 20
        return new Response(new Value($outAr, 'array'));
936
    }
937
938
    /**
939
     * @param Server $server
940
     * @param Request $req
941
     * @return Response
942
     */
943 96
    public static function _xmlrpcs_methodSignature($server, $req)
944
    {
945
        // let accept as parameter both an xmlrpc value or string
946 96
        if (is_object($req)) {
947 96
            $methName = $req->getParam(0);
948 96
            $methName = $methName->scalarval();
949
        } else {
950
            $methName = $req;
951
        }
952 96
        if ($server->isSyscall($methName)) {
953 77
            $dmap = $server->getSystemDispatchMap();
954
        } else {
955 20
            $dmap = $server->dmap;
956
        }
957 96
        if (isset($dmap[$methName])) {
958 96
            if (isset($dmap[$methName]['signature'])) {
959 96
                $sigs = array();
960 96
                foreach ($dmap[$methName]['signature'] as $inSig) {
961 96
                    $curSig = array();
962 96
                    foreach ($inSig as $sig) {
963 96
                        $curSig[] = new Value($sig, 'string');
964
                    }
965 96
                    $sigs[] = new Value($curSig, 'array');
966
                }
967 96
                $r = new Response(new Value($sigs, 'array'));
968
            } else {
969
                // NB: according to the official docs, we should be returning a
970
                // "none-array" here, which means not-an-array
971
                $r = new Response(new Value('undef', 'string'));
972
            }
973
        } else {
974
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['introspect_unknown'], PhpXmlRpc::$xmlrpcstr['introspect_unknown']);
975
        }
976
977 96
        return $r;
978
    }
979
980
    /**
981
     * @param Server $server
982
     * @param Request $req
983
     * @return Response
984
     */
985 77
    public static function _xmlrpcs_methodHelp($server, $req)
986
    {
987
        // let accept as parameter both an xmlrpc value or string
988 77
        if (is_object($req)) {
989 77
            $methName = $req->getParam(0);
990 77
            $methName = $methName->scalarval();
991
        } else {
992
            $methName = $req;
993
        }
994 77
        if ($server->isSyscall($methName)) {
995 77
            $dmap = $server->getSystemDispatchMap();
996
        } else {
997 1
            $dmap = $server->dmap;
998
        }
999 77
        if (isset($dmap[$methName])) {
1000 77
            if (isset($dmap[$methName]['docstring'])) {
1001 77
                $r = new Response(new Value($dmap[$methName]['docstring'], 'string'));
1002
            } else {
1003
                $r = new Response(new Value('', 'string'));
1004
            }
1005
        } else {
1006
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['introspect_unknown'], PhpXmlRpc::$xmlrpcstr['introspect_unknown']);
1007
        }
1008
1009 77
        return $r;
1010
    }
1011
1012 58
    public static function _xmlrpcs_multicall_error($err)
1013
    {
1014 58
        if (is_string($err)) {
1015 58
            $str = PhpXmlRpc::$xmlrpcstr["multicall_${err}"];
1016 58
            $code = PhpXmlRpc::$xmlrpcerr["multicall_${err}"];
1017
        } else {
1018 58
            $code = $err->faultCode();
1019 58
            $str = $err->faultString();
1020
        }
1021 58
        $struct = array();
1022 58
        $struct['faultCode'] = new Value($code, 'int');
1023 58
        $struct['faultString'] = new Value($str, 'string');
1024
1025 58
        return new Value($struct, 'struct');
1026
    }
1027
1028
    /**
1029
     * @param Server $server
1030
     * @param Value $call
1031
     * @return Value
1032
     */
1033 58
    public static function _xmlrpcs_multicall_do_call($server, $call)
1034
    {
1035 58
        if ($call->kindOf() != 'struct') {
1036
            return static::_xmlrpcs_multicall_error('notstruct');
1037
        }
1038 58
        $methName = @$call['methodName'];
1039 58
        if (!$methName) {
1040
            return static::_xmlrpcs_multicall_error('nomethod');
1041
        }
1042 58
        if ($methName->kindOf() != 'scalar' || $methName->scalartyp() != 'string') {
1043
            return static::_xmlrpcs_multicall_error('notstring');
1044
        }
1045 58
        if ($methName->scalarval() == 'system.multicall') {
1046 58
            return static::_xmlrpcs_multicall_error('recursion');
1047
        }
1048
1049 58
        $params = @$call['params'];
1050 58
        if (!$params) {
1051
            return static::_xmlrpcs_multicall_error('noparams');
1052
        }
1053 58
        if ($params->kindOf() != 'array') {
1054
            return static::_xmlrpcs_multicall_error('notarray');
1055
        }
1056
1057 58
        $req = new Request($methName->scalarval());
1058 58
        foreach($params as $i => $param) {
1059 58
            if (!$req->addParam($param)) {
1060
                $i++; // for error message, we count params from 1
1061
                return static::_xmlrpcs_multicall_error(new Response(0,
1062
                    PhpXmlRpc::$xmlrpcerr['incorrect_params'],
1063
                    PhpXmlRpc::$xmlrpcstr['incorrect_params'] . ": probable xml error in param " . $i));
1064
            }
1065
        }
1066
1067 58
        $result = $server->execute($req);
1068
1069 58
        if ($result->faultCode() != 0) {
1070 58
            return static::_xmlrpcs_multicall_error($result); // Method returned fault.
1071
        }
1072
1073 58
        return new Value(array($result->value()), 'array');
1074
    }
1075
1076
    /**
1077
     * @param Server $server
1078
     * @param Value $call
1079
     * @return Value
1080
     */
1081
    public static function _xmlrpcs_multicall_do_call_phpvals($server, $call)
1082
    {
1083
        if (!is_array($call)) {
0 ignored issues
show
introduced by
The condition is_array($call) is always false.
Loading history...
1084
            return static::_xmlrpcs_multicall_error('notstruct');
1085
        }
1086
        if (!array_key_exists('methodName', $call)) {
1087
            return static::_xmlrpcs_multicall_error('nomethod');
1088
        }
1089
        if (!is_string($call['methodName'])) {
1090
            return static::_xmlrpcs_multicall_error('notstring');
1091
        }
1092
        if ($call['methodName'] == 'system.multicall') {
1093
            return static::_xmlrpcs_multicall_error('recursion');
1094
        }
1095
        if (!array_key_exists('params', $call)) {
1096
            return static::_xmlrpcs_multicall_error('noparams');
1097
        }
1098
        if (!is_array($call['params'])) {
1099
            return static::_xmlrpcs_multicall_error('notarray');
1100
        }
1101
1102
        // this is a simplistic hack, since we might have received
1103
        // base64 or datetime values, but they will be listed as strings here...
1104
        $pt = array();
1105
        $wrapper = new Wrapper();
1106
        foreach ($call['params'] as $val) {
1107
            // support EPI-encoded base64 and datetime values
1108
            if ($val instanceof \stdClass && isset($val->xmlrpc_type)) {
1109
                $pt[] = $val->xmlrpc_type == 'datetime' ? Value::$xmlrpcDateTime : $val->xmlrpc_type;
1110
            } else {
1111
                $pt[] = $wrapper->php2XmlrpcType(gettype($val));
1112
            }
1113
        }
1114
1115
        $result = $server->execute($call['methodName'], $call['params'], $pt);
1116
1117
        if ($result->faultCode() != 0) {
1118
            return static::_xmlrpcs_multicall_error($result); // Method returned fault.
1119
        }
1120
1121
        return new Value(array($result->value()), 'array');
1122
    }
1123
1124
    /**
1125
     * @param Server $server
1126
     * @param Request|array $req
1127
     * @return Response
1128
     */
1129 77
    public static function _xmlrpcs_multicall($server, $req)
1130
    {
1131 77
        $result = array();
1132
        // let accept a plain list of php parameters, beside a single xmlrpc msg object
1133 77
        if (is_object($req)) {
1134 77
            $calls = $req->getParam(0);
1135 77
            foreach($calls as $call) {
1136 58
                $result[] = static::_xmlrpcs_multicall_do_call($server, $call);
1137
            }
1138
        } else {
1139
            $numCalls = count($req);
1140
            for ($i = 0; $i < $numCalls; $i++) {
1141
                $result[$i] = static::_xmlrpcs_multicall_do_call_phpvals($server, $req[$i]);
1142
            }
1143
        }
1144
1145 77
        return new Response(new Value($result, 'array'));
1146
    }
1147
1148
    /**
1149
     * Error handler used to track errors that occur during server-side execution of PHP code.
1150
     * This allows to report back to the client whether an internal error has occurred or not
1151
     * using an xmlrpc response object, instead of letting the client deal with the html junk
1152
     * that a PHP execution error on the server generally entails.
1153
     *
1154
     * NB: in fact a user defined error handler can only handle WARNING, NOTICE and USER_* errors.
1155
     */
1156 39
    public static function _xmlrpcs_errorHandler($errCode, $errString, $filename = null, $lineNo = null, $context = null)
1157
    {
1158
        // obey the @ protocol
1159 39
        if (error_reporting() == 0) {
1160 20
            return;
1161
        }
1162
1163
        //if($errCode != E_NOTICE && $errCode != E_WARNING && $errCode != E_USER_NOTICE && $errCode != E_USER_WARNING)
1164 20
        if ($errCode != E_STRICT) {
1165 20
            \PhpXmlRpc\Server::error_occurred($errString);
1166
        }
1167
        // Try to avoid as much as possible disruption to the previous error handling
1168
        // mechanism in place
1169 20
        if (self::$_xmlrpcs_prev_ehandler == '') {
1170
            // The previous error handler was the default: all we should do is log error
1171
            // to the default error log (if level high enough)
1172 20
            if (ini_get('log_errors') && (intval(ini_get('error_reporting')) & $errCode)) {
1173 20
                if (self::$logger === null) {
1174 20
                    self::$logger = Logger::instance();
1175
                }
1176 20
                self::$logger->errorLog($errString);
1177
            }
1178
        } else {
1179
            // Pass control on to previous error handler, trying to avoid loops...
1180
            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...
1181
                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...
1182
                    // the following works both with static class methods and plain object methods as error handler
1183
                    call_user_func_array(self::$_xmlrpcs_prev_ehandler, array($errCode, $errString, $filename, $lineNo, $context));
1184
                } else {
1185
                    $method = self::$_xmlrpcs_prev_ehandler;
1186
                    $method($errCode, $errString, $filename, $lineNo, $context);
1187
                }
1188
            }
1189
        }
1190 20
    }
1191
}
1192