Passed
Push — master ( 9422eb...577871 )
by Gaetano
08:48
created

Server::service()   F

Complexity

Conditions 19
Paths 1152

Size

Total Lines 82
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 24.1416

Importance

Changes 5
Bugs 1 Features 0
Metric Value
cc 19
eloc 40
c 5
b 1
f 0
nc 1152
nop 2
dl 0
loc 82
ccs 25
cts 33
cp 0.7576
crap 24.1416
rs 0.3499

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

761
                    array_unshift(/** @scrutinizer ignore-type */ $params, $this);
Loading history...
762 45
                    $r = call_user_func_array($func, $params);
763
                } else {
764
                    // 3rd API convention for method-handling functions: EPI-style
765 45
                    if ($this->functions_parameters_type == 'epivals') {
766 45
                        $r = call_user_func_array($func, array($methName, $params, $this->user_data));
767
                        // mimic EPI behaviour: if we get an array that looks like an error, make it
768
                        // an error response
769
                        if (is_array($r) && array_key_exists('faultCode', $r) && array_key_exists('faultString', $r)) {
770
                            $r = new Response(0, (integer)$r['faultCode'], (string)$r['faultString']);
771
                        } else {
772
                            // functions using EPI api should NOT return resp objects,
773
                            // so make sure we encode the return type correctly
774
                            $encoder = new Encoder();
775 45
                            $r = new Response($encoder->encode($r, array('extension_api')));
776 2
                        }
777 2
                    } else {
778
                        $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

778
                        $r = call_user_func_array($func, /** @scrutinizer ignore-type */ $params);
Loading history...
779 45
                    }
780
                }
781
                // the return type can be either a Response object or a plain php value...
782 559
                if (!is_a($r, '\PhpXmlRpc\Response')) {
783
                    // what should we assume here about automatic encoding of datetimes and php classes instances???
784
                    $encoder = new Encoder();
785 559
                    $r = new Response($encoder->encode($r, $this->phpvals_encoding_options));
786 64
                }
787
            }
788 496
        } catch (\Exception $e) {
789
            // (barring errors in the lib) an uncatched exception happened in the called function, we wrap it in a
790
            // proper error-response
791
            switch ($this->exception_handling) {
792 559
                case 2:
793
                    if ($this->debug > 2) {
794
                        if (self::$_xmlrpcs_prev_ehandler) {
795
                            set_error_handler(self::$_xmlrpcs_prev_ehandler);
796
                        } else {
797
                            restore_error_handler();
798
                        }
799
                    }
800 559
                    throw $e;
801
                case 1:
802 559
                    $errCode = $e->getCode();
803 559
                    if ($errCode == 0) {
804
                        $errCode = PhpXmlRpc::$xmlrpcerr['server_error'];
805
                    }
806
                    $r = new Response(0, $errCode, $e->getMessage());
807
                    break;
808
                default:
809 561
                    $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_error'], PhpXmlRpc::$xmlrpcstr['server_error']);
810
            }
811 561
        } catch (\Error $e) {
812 52
            // (barring errors in the lib) an uncatched exception happened in the called function, we wrap it in a
813
            // proper error-response
814 509
            switch ($this->exception_handling) {
815
                case 2:
816
                    if ($this->debug > 2) {
817
                        if (self::$_xmlrpcs_prev_ehandler) {
818
                            set_error_handler(self::$_xmlrpcs_prev_ehandler);
819
                        } else {
820
                            restore_error_handler();
821
                        }
822 559
                    }
823
                    throw $e;
824 559
                case 1:
825
                    $errCode = $e->getCode();
826
                    if ($errCode == 0) {
827
                        $errCode = PhpXmlRpc::$xmlrpcerr['server_error'];
828
                    }
829
                    $r = new Response(0, $errCode, $e->getMessage());
830
                    break;
831
                default:
832
                    $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_error'], PhpXmlRpc::$xmlrpcstr['server_error']);
833
            }
834
        }
835
        if ($this->debug > 2) {
836
            // note: restore the error handler we found before calling the
837
            // user func, even if it has been changed inside the func itself
838 127
            if (self::$_xmlrpcs_prev_ehandler) {
839
                set_error_handler(self::$_xmlrpcs_prev_ehandler);
840 127
            } else {
841
                restore_error_handler();
842
            }
843
        }
844
845
        return $r;
846 127
    }
847
848
    /**
849 127
     * Add a string to the 'internal debug message' (separate from 'user debug message').
850 127
     *
851
     * @param string $string
852
     * @return void
853
     */
854 127
    protected function debugmsg($string)
855 127
    {
856 127
        $this->debug_info .= $string . "\n";
857
    }
858
859
    /**
860 127
     * @param string $charsetEncoding
861 127
     * @return string
862 127
     */
863
    protected function xml_header($charsetEncoding = '')
864
    {
865
        if ($charsetEncoding != '') {
866 127
            return "<?xml version=\"1.0\" encoding=\"$charsetEncoding\"?" . ">\n";
867 127
        } else {
868 127
            return "<?xml version=\"1.0\"?" . ">\n";
869
        }
870
    }
871
872 127
    /**
873 127
     * @param string $methName
874 127
     * @return bool
875
     */
876
    protected function isSyscall($methName)
877
    {
878
        return (strpos($methName, "system.") === 0);
879
    }
880
881
    /**
882
     * @return array[]
883
     */
884
    public function getDispatchMap()
885
    {
886
        return $this->dmap;
887
    }
888
889
    /**
890
     * @return array[]
891
     */
892
    public function getSystemDispatchMap()
893
    {
894
        if (!$this->allow_system_funcs) {
895
            return array();
896
        }
897
898
        return array(
899
            'system.listMethods' => array(
900
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_listMethods',
901
                // listMethods: signature was either a string, or nothing.
902
                // The useless string variant has been removed
903
                'signature' => array(array(Value::$xmlrpcArray)),
904
                'docstring' => 'This method lists all the methods that the XML-RPC server knows how to dispatch',
905
                'signature_docs' => array(array('list of method names')),
906
            ),
907
            'system.methodHelp' => array(
908
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_methodHelp',
909
                'signature' => array(array(Value::$xmlrpcString, Value::$xmlrpcString)),
910
                'docstring' => 'Returns help text if defined for the method passed, otherwise returns an empty string',
911
                'signature_docs' => array(array('method description', 'name of the method to be described')),
912
            ),
913
            'system.methodSignature' => array(
914
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_methodSignature',
915
                'signature' => array(array(Value::$xmlrpcArray, Value::$xmlrpcString)),
916
                '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)',
917
                'signature_docs' => array(array('list of known signatures, each sig being an array of xmlrpc type names', 'name of method to be described')),
918
            ),
919
            'system.multicall' => array(
920
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_multicall',
921
                'signature' => array(array(Value::$xmlrpcArray, Value::$xmlrpcArray)),
922
                'docstring' => 'Boxcar multiple RPC calls in one request. See http://www.xmlrpc.com/discuss/msgReader$1208 for details',
923
                '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"')),
924
            ),
925
            'system.getCapabilities' => array(
926
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_getCapabilities',
927
                'signature' => array(array(Value::$xmlrpcStruct)),
928
                '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',
929
                'signature_docs' => array(array('list of capabilities, described as structs with a version number and url for the spec')),
930
            ),
931
        );
932
    }
933 22
934
    /**
935 22
     * @return array[]
936 22
     */
937 22
    public function getCapabilities()
938
    {
939 22
        $outAr = array(
940 22
            // xmlrpc spec: always supported
941
            'xmlrpc' => array(
942
                'specUrl' => 'http://www.xmlrpc.com/spec',
943 22
                'specVersion' => 1
944
            ),
945
            // if we support system.xxx functions, we always support multicall, too...
946
            // Note that, as of 2006/09/17, the following URL does not respond anymore
947
            'system.multicall' => array(
948
                'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
949
                'specVersion' => 1
950
            ),
951 106
            // introspection: version 2! we support 'mixed', too
952
            'introspection' => array(
953
                'specUrl' => 'http://phpxmlrpc.sourceforge.net/doc-2/ch10.html',
954 106
                'specVersion' => 2,
955 106
            ),
956 106
        );
957
958
        // NIL extension
959
        if (PhpXmlRpc::$xmlrpc_null_extension) {
960 106
            $outAr['nil'] = array(
961 85
                'specUrl' => 'http://www.ontosys.com/xml-rpc/extensions.php',
962
                'specVersion' => 1
963 22
            );
964
        }
965 106
966 106
        return $outAr;
967 106
    }
968 106
969 106
    /**
970 106
     * @internal handler of a system. method
971 106
     *
972
     * @param Server $server
973 106
     * @param Request $req
974
     * @return Response
975 106
     */
976
    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

976
    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...
977
    {
978
        $encoder = new Encoder();
979
        return new Response($encoder->encode($server->getCapabilities()));
980
    }
981
982 1
    /**
983
     * @internal handler of a system. method
984
     *
985 106
     * @param Server $server
986
     * @param Request $req if called in plain php values mode, second param is missing
987
     * @return Response
988
     */
989
    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

989
    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...
990
    {
991
        $outAr = array();
992
        foreach ($server->dmap as $key => $val) {
993 85
            $outAr[] = new Value($key, 'string');
994
        }
995
        foreach ($server->getSystemDispatchMap() as $key => $val) {
996 85
            $outAr[] = new Value($key, 'string');
997 85
        }
998 85
999
        return new Response(new Value($outAr, 'array'));
1000
    }
1001
1002 85
    /**
1003 85
     * @internal handler of a system. method
1004
     *
1005 1
     * @param Server $server
1006
     * @param Request $req
1007 85
     * @return Response
1008 85
     */
1009 85
    public static function _xmlrpcs_methodSignature($server, $req)
1010
    {
1011
        // let's accept as parameter either an xmlrpc value or string
1012
        if (is_object($req)) {
1013
            $methName = $req->getParam(0);
1014
            $methName = $methName->scalarval();
1015
        } else {
1016
            $methName = $req;
1017 85
        }
1018
        if ($server->isSyscall($methName)) {
1019
            $dmap = $server->getSystemDispatchMap();
1020 64
        } else {
1021
            $dmap = $server->dmap;
1022 64
        }
1023 64
        if (isset($dmap[$methName])) {
1024 64
            if (isset($dmap[$methName]['signature'])) {
1025
                $sigs = array();
1026 64
                foreach ($dmap[$methName]['signature'] as $inSig) {
1027 64
                    $curSig = array();
1028
                    foreach ($inSig as $sig) {
1029 64
                        $curSig[] = new Value($sig, 'string');
1030 64
                    }
1031 64
                    $sigs[] = new Value($curSig, 'array');
1032
                }
1033 64
                $r = new Response(new Value($sigs, 'array'));
1034
            } else {
1035
                // NB: according to the official docs, we should be returning a
1036
                // "none-array" here, which means not-an-array
1037
                $r = new Response(new Value('undef', 'string'));
1038
            }
1039
        } else {
1040
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['introspect_unknown'], PhpXmlRpc::$xmlrpcstr['introspect_unknown']);
1041 64
        }
1042
1043 64
        return $r;
1044
    }
1045
1046 64
    /**
1047 64
     * @internal handler of a system. method
1048
     *
1049
     * @param Server $server
1050 64
     * @param Request $req
1051
     * @return Response
1052
     */
1053 64
    public static function _xmlrpcs_methodHelp($server, $req)
1054 64
    {
1055
        // let's accept as parameter either an xmlrpc value or string
1056
        if (is_object($req)) {
1057 64
            $methName = $req->getParam(0);
1058 64
            $methName = $methName->scalarval();
1059
        } else {
1060
            $methName = $req;
1061 64
        }
1062
        if ($server->isSyscall($methName)) {
1063
            $dmap = $server->getSystemDispatchMap();
1064
        } else {
1065 64
            $dmap = $server->dmap;
1066 64
        }
1067 64
        if (isset($dmap[$methName])) {
1068
            if (isset($dmap[$methName]['docstring'])) {
1069
                $r = new Response(new Value($dmap[$methName]['docstring'], 'string'));
1070
            } else {
1071
                $r = new Response(new Value('', 'string'));
1072
            }
1073
        } else {
1074
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['introspect_unknown'], PhpXmlRpc::$xmlrpcstr['introspect_unknown']);
1075 64
        }
1076
1077 64
        return $r;
1078 64
    }
1079
1080
    /**
1081 64
     * @internal this function will become protected in the future
1082
     *
1083
     * @param $err
1084
     * @return Value
1085
     */
1086
    public static function _xmlrpcs_multicall_error($err)
1087
    {
1088
        if (is_string($err)) {
1089
            $str = PhpXmlRpc::$xmlrpcstr["multicall_{$err}"];
1090
            $code = PhpXmlRpc::$xmlrpcerr["multicall_{$err}"];
1091
        } else {
1092
            $code = $err->faultCode();
1093
            $str = $err->faultString();
1094
        }
1095
        $struct = array();
1096
        $struct['faultCode'] = new Value($code, 'int');
1097
        $struct['faultString'] = new Value($str, 'string');
1098
1099
        return new Value($struct, 'struct');
1100
    }
1101
1102
    /**
1103
     * @internal this function will become protected in the future
1104
     *
1105
     * @param Server $server
1106
     * @param Value $call
1107
     * @return Value
1108
     */
1109
    public static function _xmlrpcs_multicall_do_call($server, $call)
1110
    {
1111
        if ($call->kindOf() != 'struct') {
1112
            return static::_xmlrpcs_multicall_error('notstruct');
1113
        }
1114
        $methName = @$call['methodName'];
1115
        if (!$methName) {
1116
            return static::_xmlrpcs_multicall_error('nomethod');
1117
        }
1118
        if ($methName->kindOf() != 'scalar' || $methName->scalartyp() != 'string') {
1119
            return static::_xmlrpcs_multicall_error('notstring');
1120
        }
1121
        if ($methName->scalarval() == 'system.multicall') {
1122
            return static::_xmlrpcs_multicall_error('recursion');
1123
        }
1124
1125
        $params = @$call['params'];
1126
        if (!$params) {
1127
            return static::_xmlrpcs_multicall_error('noparams');
1128
        }
1129
        if ($params->kindOf() != 'array') {
1130
            return static::_xmlrpcs_multicall_error('notarray');
1131
        }
1132
1133
        $req = new Request($methName->scalarval());
1134
        foreach ($params as $i => $param) {
1135
            if (!$req->addParam($param)) {
1136
                $i++; // for error message, we count params from 1
1137 85
                return static::_xmlrpcs_multicall_error(new Response(0,
1138
                    PhpXmlRpc::$xmlrpcerr['incorrect_params'],
1139 85
                    PhpXmlRpc::$xmlrpcstr['incorrect_params'] . ": probable xml error in param " . $i));
1140
            }
1141 85
        }
1142 85
1143 85
        $result = $server->execute($req);
1144 64
1145
        if ($result->faultCode() != 0) {
1146
            return static::_xmlrpcs_multicall_error($result); // Method returned fault.
1147
        }
1148
1149
        return new Value(array($result->value()), 'array');
1150
    }
1151
1152
    /**
1153 85
     * @internal this function will become protected in the future
1154
     *
1155
     * @param Server $server
1156
     * @param Value $call
1157
     * @return Value
1158
     */
1159
    public static function _xmlrpcs_multicall_do_call_phpvals($server, $call)
1160
    {
1161
        if (!is_array($call)) {
0 ignored issues
show
introduced by
The condition is_array($call) is always false.
Loading history...
1162
            return static::_xmlrpcs_multicall_error('notstruct');
1163
        }
1164 43
        if (!array_key_exists('methodName', $call)) {
1165
            return static::_xmlrpcs_multicall_error('nomethod');
1166
        }
1167 43
        if (!is_string($call['methodName'])) {
1168 22
            return static::_xmlrpcs_multicall_error('notstring');
1169
        }
1170
        if ($call['methodName'] == 'system.multicall') {
1171
            return static::_xmlrpcs_multicall_error('recursion');
1172 22
        }
1173 22
        if (!array_key_exists('params', $call)) {
1174
            return static::_xmlrpcs_multicall_error('noparams');
1175
        }
1176
        if (!is_array($call['params'])) {
1177 22
            return static::_xmlrpcs_multicall_error('notarray');
1178
        }
1179
1180 22
        // this is a simplistic hack, since we might have received
1181 22
        // base64 or datetime values, but they will be listed as strings here...
1182 22
        $pt = array();
1183
        $wrapper = new Wrapper();
1184 22
        foreach ($call['params'] as $val) {
1185
            // support EPI-encoded base64 and datetime values
1186
            if ($val instanceof \stdClass && isset($val->xmlrpc_type)) {
1187
                $pt[] = $val->xmlrpc_type == 'datetime' ? Value::$xmlrpcDateTime : $val->xmlrpc_type;
1188
            } else {
1189
                $pt[] = $wrapper->php2XmlrpcType(gettype($val));
1190
            }
1191
        }
1192
1193
        $result = $server->execute($call['methodName'], $call['params'], $pt);
1194
1195
        if ($result->faultCode() != 0) {
1196
            return static::_xmlrpcs_multicall_error($result); // Method returned fault.
1197
        }
1198 22
1199
        return new Value(array($result->value()), 'array');
1200
    }
1201
1202
    /**
1203
     * @internal handler of a system. method
1204
     *
1205
     * @param Server $server
1206
     * @param Request|array $req
1207
     * @return Response
1208
     */
1209
    public static function _xmlrpcs_multicall($server, $req)
1210
    {
1211
        $result = array();
1212
        // let accept a plain list of php parameters, beside a single xmlrpc msg object
1213
        if (is_object($req)) {
1214
            $calls = $req->getParam(0);
1215
            foreach ($calls as $call) {
1216
                $result[] = static::_xmlrpcs_multicall_do_call($server, $call);
1217
            }
1218
        } else {
1219
            $numCalls = count($req);
1220
            for ($i = 0; $i < $numCalls; $i++) {
1221
                $result[$i] = static::_xmlrpcs_multicall_do_call_phpvals($server, $req[$i]);
1222
            }
1223
        }
1224
1225
        return new Response(new Value($result, 'array'));
1226
    }
1227
1228
    /**
1229
     * Error handler used to track errors that occur during server-side execution of PHP code.
1230
     * This allows to report back to the client whether an internal error has occurred or not
1231
     * using an xmlrpc response object, instead of letting the client deal with the html junk
1232
     * that a PHP execution error on the server generally entails.
1233
     *
1234
     * NB: in fact a user defined error handler can only handle WARNING, NOTICE and USER_* errors.
1235
     *
1236
     * @internal
1237
     */
1238
    public static function _xmlrpcs_errorHandler($errCode, $errString, $filename = null, $lineNo = null, $context = null)
1239
    {
1240
        // obey the @ protocol
1241
        if (error_reporting() == 0) {
1242
            return;
1243
        }
1244
1245
        //if ($errCode != E_NOTICE && $errCode != E_WARNING && $errCode != E_USER_NOTICE && $errCode != E_USER_WARNING)
1246
        if ($errCode != E_STRICT) {
1247
            static::error_occurred($errString);
1248
        }
1249
        // Try to avoid as much as possible disruption to the previous error handling
1250
        // mechanism in place
1251
        if (self::$_xmlrpcs_prev_ehandler == '') {
1252
            // The previous error handler was the default: all we should do is log error
1253
            // to the default error log (if level high enough)
1254
            if (ini_get('log_errors') && (intval(ini_get('error_reporting')) & $errCode)) {
1255
                if (self::$logger === null) {
1256
                    self::$logger = Logger::instance();
1257
                }
1258
                self::$logger->errorLog($errString);
1259
            }
1260
        } else {
1261
            // Pass control on to previous error handler, trying to avoid loops...
1262
            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...
1263
                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...
1264
                    // the following works both with static class methods and plain object methods as error handler
1265
                    call_user_func_array(self::$_xmlrpcs_prev_ehandler, array($errCode, $errString, $filename, $lineNo, $context));
1266
                } else {
1267
                    $method = self::$_xmlrpcs_prev_ehandler;
1268
                    $method($errCode, $errString, $filename, $lineNo, $context);
1269
                }
1270
            }
1271
        }
1272
    }
1273
}
1274