Passed
Push — master ( b9401f...4e5288 )
by Gaetano
06:45
created

Server::_xmlrpcs_getCapabilities()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 2
b 0
f 0
nc 1
nop 2
dl 0
loc 4
ccs 0
cts 3
cp 0
crap 2
rs 10
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 510
    public function getParser()
116
    {
117 510
        if (self::$parser === null) {
118 510
            self::$parser = new XMLParser();
119
        }
120 510
        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 510
    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 510
        if (function_exists('gzinflate')) {
157 510
            $this->accepted_compression = array('gzip', 'deflate');
158 510
            $this->compress_response = true;
159
        }
160
161
        // by default the xml parser can support these 3 charset encodings
162 510
        $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 510
        if ($dispatchMap) {
171 509
            $this->dmap = $dispatchMap;
172 509
            if ($serviceNow) {
173 2
                $this->service();
174
            }
175
        }
176 510
    }
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 507
    public function setDebug($level)
193
    {
194 507
        $this->debug = $level;
195 507
    }
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 509
    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 509
        $out = '';
236 509
        if ($this->debug_info != '') {
237 507
            $out .= "<!-- SERVER DEBUG INFO (BASE64 ENCODED):\n" . base64_encode($this->debug_info) . "\n-->\n";
238
        }
239 509
        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 509
        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 509
    public function service($data = null, $returnPayload = false)
260
    {
261 509
        if ($data === null) {
262 509
            $data = file_get_contents('php://input');
263
        }
264 509
        $rawData = $data;
265
266
        // reset internal debug info
267 509
        $this->debug_info = '';
268
269
        // Save what we received, before parsing it
270 509
        if ($this->debug > 1) {
271 507
            $this->debugmsg("+++GOT+++\n" . $data . "\n+++END+++");
272
        }
273
274 509
        $r = $this->parseRequestHeaders($data, $reqCharset, $respCharset, $respEncoding);
275 509
        if (!$r) {
276
            // this actually executes the request
277 509
            $r = $this->parseRequest($data, $reqCharset);
278
279
            // save full body of request into response, for more debugging usages.
280
            // Note that this is the _request_ data, not the response's own data, unlike what happens client-side
281
            /// @todo try to move this injection to the resp. constructor or use a non-deprecated access method
282 509
            $r->raw_data = $rawData;
283
        }
284
285 509
        if ($this->debug > 2 && static::$_xmlrpcs_occurred_errors) {
286 20
            $this->debugmsg("+++PROCESSING ERRORS AND WARNINGS+++\n" .
287 20
                static::$_xmlrpcs_occurred_errors . "+++END+++");
288
        }
289
290 509
        $payload = $this->xml_header($respCharset);
291 509
        if ($this->debug > 0) {
292 509
            $payload = $payload . $this->serializeDebug($respCharset);
293
        }
294
295
        // Do not create response serialization if it has already happened. Helps building json magic
296 509
        if (empty($r->payload)) {
297 509
            $r->serialize($respCharset);
298
        }
299 509
        $payload = $payload . $r->payload;
300
301 509
        if ($returnPayload) {
302
            return $payload;
303
        }
304
305
        // if we get a warning/error that has output some text before here, then we cannot
306
        // add a new header. We cannot say we are sending xml, either...
307 509
        if (!headers_sent()) {
308 509
            header('Content-Type: ' . $r->content_type);
309
            // we do not know if client actually told us an accepted charset, but if he did
310
            // we have to tell him what we did
311 509
            header("Vary: Accept-Charset");
312
313
            // http compression of output: only
314
            // if we can do it, and we want to do it, and client asked us to,
315
            // and php ini settings do not force it already
316
            /// @todo check separately for gzencode and gzcompress functions, in case of polyfills
317 509
            $phpNoSelfCompress = !ini_get('zlib.output_compression') && (ini_get('output_handler') != 'ob_gzhandler');
318 509
            if ($this->compress_response && function_exists('gzencode') && $respEncoding != ''
319 104
                && $phpNoSelfCompress
320
            ) {
321 104
                if (strpos($respEncoding, 'gzip') !== false) {
322 52
                    $payload = gzencode($payload);
323 52
                    header("Content-Encoding: gzip");
324 52
                    header("Vary: Accept-Encoding");
325 52
                } elseif (strpos($respEncoding, 'deflate') !== false) {
326 52
                    $payload = gzcompress($payload);
327 52
                    header("Content-Encoding: deflate");
328 52
                    header("Vary: Accept-Encoding");
329
                }
330
            }
331
332
            // Do not output content-length header if php is compressing output for us:
333
            // it will mess up measurements.
334
            // Note that Apache/mod_php will add (and even alter!) the Content-Length header on its own, but only for
335
            // responses up to 8000 bytes
336 509
            if ($phpNoSelfCompress) {
337 509
                header('Content-Length: ' . (int)strlen($payload));
338
            }
339
        } else {
340
            $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': http headers already sent before response is fully generated. Check for php warning or error messages');
341
        }
342
343 509
        print $payload;
344
345
        // return request, in case subclasses want it
346 509
        return $r;
347
    }
348
349
    /**
350
     * Add a method to the dispatch map.
351
     *
352
     * @param string $methodName the name with which the method will be made available
353
     * @param callable $function the php function that will get invoked
354
     * @param array[] $sig the array of valid method signatures.
355
     *                     Each element is one signature: an array of strings with at least one element
356
     *                     First element = type of returned value. Elements 2..N = types of parameters 1..N
357
     * @param string $doc method documentation
358
     * @param array[] $sigDoc the array of valid method signatures docs, following the format of $sig but with
359
     *                        descriptions instead of types (one string for return type, one per param)
360
     *
361
     * @todo raise a warning if the user tries to register a 'system.' method
362
     * @todo allow setting parameters_type
363
     */
364
    public function add_to_map($methodName, $function, $sig = null, $doc = false, $sigDoc = false)
365
    {
366
        $this->dmap[$methodName] = array(
367
            'function' => $function,
368
            'docstring' => $doc,
369
        );
370
        if ($sig) {
371
            $this->dmap[$methodName]['signature'] = $sig;
372
        }
373
        if ($sigDoc) {
374
            $this->dmap[$methodName]['signature_docs'] = $sigDoc;
375
        }
376
    }
377
378
    /**
379
     * Verify type and number of parameters received against a list of known signatures.
380
     *
381
     * @param array|Request $in array of either xmlrpc value objects or xmlrpc type definitions
382
     * @param array $sigs array of known signatures to match against
383
     *
384
     * @return array int, string
385
     */
386 486
    protected function verifySignature($in, $sigs)
387
    {
388
        // check each possible signature in turn
389 486
        if (is_object($in)) {
390 486
            $numParams = $in->getNumParams();
391
        } else {
392
            $numParams = count($in);
393
        }
394 486
        foreach ($sigs as $curSig) {
395 486
            if (count($curSig) == $numParams + 1) {
396 486
                $itsOK = 1;
397 486
                for ($n = 0; $n < $numParams; $n++) {
398 467
                    if (is_object($in)) {
399 467
                        $p = $in->getParam($n);
400 467
                        if ($p->kindOf() == 'scalar') {
401 410
                            $pt = $p->scalartyp();
402
                        } else {
403 134
                            $pt = $p->kindOf();
404
                        }
405
                    } else {
406
                        $pt = ($in[$n] == 'i4') ? 'int' : strtolower($in[$n]); // dispatch maps never use i4...
407
                    }
408
409
                    // param index is $n+1, as first member of sig is return type
410 467
                    if ($pt != $curSig[$n + 1] && $curSig[$n + 1] != Value::$xmlrpcValue) {
411 20
                        $itsOK = 0;
412 20
                        $pno = $n + 1;
413 20
                        $wanted = $curSig[$n + 1];
414 20
                        $got = $pt;
415 20
                        break;
416
                    }
417
                }
418 486
                if ($itsOK) {
419 486
                    return array(1, '');
420
                }
421
            }
422
        }
423 20
        if (isset($wanted)) {
424
            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...
425
        } else {
426 20
            return array(0, "No method signature matches number of parameters");
427
        }
428
    }
429
430
    /**
431
     * Parse http headers received along with xmlrpc request. If needed, inflate request.
432
     *
433
     * @return Response|null null on success or an error Response
434
     */
435 509
    protected function parseRequestHeaders(&$data, &$reqEncoding, &$respEncoding, &$respCompression)
436
    {
437
        // check if $_SERVER is populated: it might have been disabled via ini file
438
        // (this is true even when in CLI mode)
439 509
        if (count($_SERVER) == 0) {
440
            $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': cannot parse request headers as $_SERVER is not populated');
441
        }
442
443 509
        if ($this->debug > 1) {
444 507
            if (function_exists('getallheaders')) {
445 507
                $this->debugmsg(''); // empty line
446 507
                foreach (getallheaders() as $name => $val) {
447 507
                    $this->debugmsg("HEADER: $name: $val");
448
                }
449
            }
450
        }
451
452 509
        if (isset($_SERVER['HTTP_CONTENT_ENCODING'])) {
453 104
            $contentEncoding = str_replace('x-', '', $_SERVER['HTTP_CONTENT_ENCODING']);
454
        } else {
455 405
            $contentEncoding = '';
456
        }
457
458 509
        $rawData = $data;
459
460
        // check if request body has been compressed and decompress it
461 509
        if ($contentEncoding != '' && strlen($data)) {
462 104
            if ($contentEncoding == 'deflate' || $contentEncoding == 'gzip') {
463
                // if decoding works, use it. else assume data wasn't gzencoded
464 104
                if (function_exists('gzinflate') && in_array($contentEncoding, $this->accepted_compression)) {
465 104
                    if ($contentEncoding == 'deflate' && $degzdata = @gzuncompress($data)) {
466 52
                        $data = $degzdata;
467 52
                        if ($this->debug > 1) {
468 52
                            $this->debugmsg("\n+++INFLATED REQUEST+++[" . strlen($data) . " chars]+++\n" . $data . "\n+++END+++");
469
                        }
470 52
                    } elseif ($contentEncoding == 'gzip' && $degzdata = @gzinflate(substr($data, 10))) {
471 52
                        $data = $degzdata;
472 52
                        if ($this->debug > 1) {
473 52
                            $this->debugmsg("+++INFLATED REQUEST+++[" . strlen($data) . " chars]+++\n" . $data . "\n+++END+++");
474
                        }
475
                    } else {
476
                        $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_decompress_fail'],
477
                            PhpXmlRpc::$xmlrpcstr['server_decompress_fail'], '', array('raw_data' => $rawData)
478
                        );
479
480
                        return $r;
481
                    }
482
                } else {
483
                    $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_cannot_decompress'],
484
                        PhpXmlRpc::$xmlrpcstr['server_cannot_decompress'], '', array('raw_data' => $rawData)
485
                    );
486
487
                    return $r;
488
                }
489
            }
490
        }
491
492
        // check if client specified accepted charsets, and if we know how to fulfill
493
        // the request
494 509
        if ($this->response_charset_encoding == 'auto') {
495
            $respEncoding = '';
496
            if (isset($_SERVER['HTTP_ACCEPT_CHARSET'])) {
497
                // here we should check if we can match the client-requested encoding
498
                // with the encodings we know we can generate.
499
                /// @todo we should parse q=0.x preferences instead of getting first charset specified...
500
                $clientAcceptedCharsets = explode(',', strtoupper($_SERVER['HTTP_ACCEPT_CHARSET']));
501
                // Give preference to internal encoding
502
                $knownCharsets = array(PhpXmlRpc::$xmlrpc_internalencoding, 'UTF-8', 'ISO-8859-1', 'US-ASCII');
503
                foreach ($knownCharsets as $charset) {
504
                    foreach ($clientAcceptedCharsets as $accepted) {
505
                        if (strpos($accepted, $charset) === 0) {
506
                            $respEncoding = $charset;
507
                            break;
508
                        }
509
                    }
510
                    if ($respEncoding) {
511
                        break;
512
                    }
513
                }
514
            }
515
        } else {
516 509
            $respEncoding = $this->response_charset_encoding;
517
        }
518
519 509
        if (isset($_SERVER['HTTP_ACCEPT_ENCODING'])) {
520 104
            $respCompression = $_SERVER['HTTP_ACCEPT_ENCODING'];
521
        } else {
522 405
            $respCompression = '';
523
        }
524
525
        // 'guestimate' request encoding
526
        /// @todo check if mbstring is enabled and automagic input conversion is on: it might mingle with this check???
527 509
        $reqEncoding = XMLParser::guessEncoding(isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : '',
528
            $data);
529
530 509
        return null;
531
    }
532
533
    /**
534
     * Parse an xml chunk containing an xmlrpc request and execute the corresponding
535
     * php function registered with the server.
536
     *
537
     * @param string $data the xml request
538
     * @param string $reqEncoding (optional) the charset encoding of the xml request
539
     *
540
     * @return Response
541
     *
542
     * @throws \Exception in case the executed method does throw an exception (and depending on server configuration)
543
     *
544
     * @internal this function will become protected in the future
545
     * @todo either rename this function or move the 'execute' part out of it...
546
     */
547 510
    public function parseRequest($data, $reqEncoding = '')
548
    {
549
        // decompose incoming XML into request structure
550
551 510
        if ($reqEncoding != '') {
552
            // Since parsing will fail if
553
            // - charset is not specified in the xml prologue,
554
            // - the encoding is not UTF8 and
555
            // - there are non-ascii chars in the text,
556
            // we try to work round that...
557
            // The following code might be better for mb_string enabled installs, but
558
            // makes the lib about 200% slower...
559
            //if (!is_valid_charset($reqEncoding, array('UTF-8')))
560 509
            if (!in_array($reqEncoding, array('UTF-8', 'US-ASCII')) && !XMLParser::hasEncoding($data)) {
561 4
                if ($reqEncoding == 'ISO-8859-1') {
562 2
                    $data = utf8_encode($data);
563
                } else {
564 2
                    if (extension_loaded('mbstring')) {
565 2
                        $data = mb_convert_encoding($data, 'UTF-8', $reqEncoding);
566
                    } else {
567
                        $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': invalid charset encoding of received request: ' . $reqEncoding);
568
                    }
569
                }
570
            }
571
        }
572
573
        // PHP internally might use ISO-8859-1, so we have to tell the xml parser to give us back data in the expected charset.
574
        // What if internal encoding is not in one of the 3 allowed? We use the broadest one, ie. utf8
575
        // This allows to send data which is native in various charset,
576
        // by extending xmlrpc_encode_entities() and setting xmlrpc_internalencoding
577 510
        if (!in_array(PhpXmlRpc::$xmlrpc_internalencoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) {
578
            /// @todo emit a warning
579
            $options = array(XML_OPTION_TARGET_ENCODING => 'UTF-8');
580
        } else {
581 510
            $options = array(XML_OPTION_TARGET_ENCODING => PhpXmlRpc::$xmlrpc_internalencoding);
582
        }
583
584 510
        $xmlRpcParser = $this->getParser();
585 510
        $xmlRpcParser->parse($data, $this->functions_parameters_type, XMLParser::ACCEPT_REQUEST, $options);
586 510
        if ($xmlRpcParser->_xh['isf'] > 2) {
587
            // (BC) we return XML error as a faultCode
588 2
            preg_match('/^XML error ([0-9]+)/', $xmlRpcParser->_xh['isf_reason'], $matches);
589 2
            $r = new Response(0,
590 2
                PhpXmlRpc::$xmlrpcerrxml + $matches[1],
591 2
                $xmlRpcParser->_xh['isf_reason']);
592 508
        } elseif ($xmlRpcParser->_xh['isf']) {
593 1
            $r = new Response(0,
594 1
                PhpXmlRpc::$xmlrpcerr['invalid_request'],
595 1
                PhpXmlRpc::$xmlrpcstr['invalid_request'] . ' ' . $xmlRpcParser->_xh['isf_reason']);
596
        } else {
597
            // small layering violation in favor of speed and memory usage:
598
            // we should allow the 'execute' method handle this, but in the
599
            // most common scenario (xmlrpc values type server with some methods
600
            // registered as phpvals) that would mean a useless encode+decode pass
601 507
            if ($this->functions_parameters_type != 'xmlrpcvals' ||
602 507
                (isset($this->dmap[$xmlRpcParser->_xh['method']]['parameters_type']) &&
603
                    ($this->dmap[$xmlRpcParser->_xh['method']]['parameters_type'] != 'xmlrpcvals')
604
                )
605
            ) {
606
                if ($this->debug > 1) {
607
                    $this->debugmsg("\n+++PARSED+++\n" . var_export($xmlRpcParser->_xh['params'], true) . "\n+++END+++");
608
                }
609
                $r = $this->execute($xmlRpcParser->_xh['method'], $xmlRpcParser->_xh['params'], $xmlRpcParser->_xh['pt']);
610
            } else {
611
                // build a Request object with data parsed from xml
612 507
                $req = new Request($xmlRpcParser->_xh['method']);
613
                // now add parameters in
614 507
                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...
615 488
                    $req->addParam($xmlRpcParser->_xh['params'][$i]);
616
                }
617
618 507
                if ($this->debug > 1) {
619 507
                    $this->debugmsg("\n+++PARSED+++\n" . var_export($req, true) . "\n+++END+++");
620
                }
621 507
                $r = $this->execute($req);
622
            }
623
        }
624
625 510
        return $r;
626
    }
627
628
    /**
629
     * Execute a method invoked by the client, checking parameters used.
630
     *
631
     * @param Request|string $req either a Request obj or a method name
632
     * @param mixed[] $params array with method parameters as php types (only if m is method name)
633
     * @param string[] $paramTypes array with xmlrpc types of method parameters (only if m is method name)
634
     *
635
     * @return Response
636
     *
637
     * @throws \Exception in case the executed method does throw an exception (and depending on server configuration)
638
     */
639 507
    protected function execute($req, $params = null, $paramTypes = null)
640
    {
641 507
        static::$_xmlrpcs_occurred_errors = '';
642 507
        static::$_xmlrpc_debuginfo = '';
643
644 507
        if (is_object($req)) {
645 507
            $methName = $req->method();
646
        } else {
647
            $methName = $req;
648
        }
649 507
        $sysCall = $this->isSyscall($methName);
650 507
        $dmap = $sysCall ? $this->getSystemDispatchMap() : $this->dmap;
651
652 507
        if (!isset($dmap[$methName]['function'])) {
653
            // No such method
654 77
            return new Response(0,
655 77
                PhpXmlRpc::$xmlrpcerr['unknown_method'],
656 77
                PhpXmlRpc::$xmlrpcstr['unknown_method']);
657
        }
658
659
        // Check signature
660 507
        if (isset($dmap[$methName]['signature'])) {
661 486
            $sig = $dmap[$methName]['signature'];
662 486
            if (is_object($req)) {
663 486
                list($ok, $errStr) = $this->verifySignature($req, $sig);
664
            } else {
665
                list($ok, $errStr) = $this->verifySignature($paramTypes, $sig);
666
            }
667 486
            if (!$ok) {
668
                // Didn't match.
669 20
                return new Response(
670 20
                    0,
671 20
                    PhpXmlRpc::$xmlrpcerr['incorrect_params'],
672 20
                    PhpXmlRpc::$xmlrpcstr['incorrect_params'] . ": ${errStr}"
673
                );
674
            }
675
        }
676
677 507
        $func = $dmap[$methName]['function'];
678
        // let the 'class::function' syntax be accepted in dispatch maps
679 507
        if (is_string($func) && strpos($func, '::')) {
680 115
            $func = explode('::', $func);
681
        }
682
683 507
        if (is_array($func)) {
684 136
            if (is_object($func[0])) {
685 22
                $funcName = get_class($func[0]) . '->' . $func[1];
686
            } else {
687 115
                $funcName = implode('::', $func);
688
            }
689 391
        } else if ($func instanceof \Closure) {
690 117
            $funcName = 'Closure';
691
        } else {
692 294
            $funcName = $func;
693
        }
694
695
        // verify that function to be invoked is in fact callable
696 507
        if (!is_callable($func)) {
697
            $this->getLogger()->errorLog("XML-RPC: " . __METHOD__ . ": function '$funcName' registered as method handler is not callable");
698
            return new Response(
699
                0,
700
                PhpXmlRpc::$xmlrpcerr['server_error'],
701
                PhpXmlRpc::$xmlrpcstr['server_error'] . ": no function matches method"
702
            );
703
        }
704
705
        // If debug level is 3, we should catch all errors generated during
706
        // processing of user function, and log them as part of response
707 507
        if ($this->debug > 2) {
708 507
            self::$_xmlrpcs_prev_ehandler = set_error_handler(array('\PhpXmlRpc\Server', '_xmlrpcs_errorHandler'));
709
        }
710
711
        try {
712
            // Allow mixed-convention servers
713 507
            if (is_object($req)) {
714 507
                if ($sysCall) {
715 115
                    $r = call_user_func($func, $this, $req);
716
                } else {
717 412
                    $r = call_user_func($func, $req);
718
                }
719 505
                if (!is_a($r, 'PhpXmlRpc\Response')) {
720
                    $this->getLogger()->errorLog("XML-RPC: " . __METHOD__ . ": function '$funcName' registered as method handler does not return an xmlrpc response object but a " . gettype($r));
721
                    if (is_a($r, 'PhpXmlRpc\Value')) {
722
                        $r = new Response($r);
723
                    } else {
724
                        $r = new Response(
725
                            0,
726
                            PhpXmlRpc::$xmlrpcerr['server_error'],
727
                            PhpXmlRpc::$xmlrpcstr['server_error'] . ": function does not return xmlrpc response object"
728
                        );
729
                    }
730
                }
731
            } else {
732
                // call a 'plain php' function
733
                if ($sysCall) {
734
                    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

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

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

922
    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...
923
    {
924
        $encoder = new Encoder();
925
        return new Response($encoder->encode($server->getCapabilities()));
926
    }
927
928
    /**
929
     * @param Server $server
930
     * @param Request $req if called in plain php values mode, second param is missing
931
     * @return Response
932
     */
933 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

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