Completed
Push — master ( 252344...1eeaee )
by Gaetano
04:02 queued 11s
created

Server::verifySignature()   C

Complexity

Conditions 12
Paths 62

Size

Total Lines 43

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 12.2487

Importance

Changes 0
Metric Value
cc 12
nc 62
nop 2
dl 0
loc 43
ccs 22
cts 25
cp 0.88
crap 12.2487
rs 6.9666
c 0
b 0
f 0

How to fix   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\XMLParser;
6
use PhpXmlRpc\Helper\Charset;
7
8
/**
9
 * Allows effortless implementation of XML-RPC servers
10
 */
11
class Server
12
{
13
    /**
14
     * Array defining php functions exposed as xmlrpc methods by this server.
15
     */
16
    protected $dmap = array();
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
     */
23
    public $functions_parameters_type = 'xmlrpcvals';
24
25
    /**
26
     * Option used for fine-tuning the encoding the php values returned from
27
     * functions registered in the dispatch map when the functions_parameters_types
28
     * member is set to 'phpvals'
29
     * @see Encoder::encode for a list of values
30
     */
31
    public $phpvals_encoding_options = array('auto_dates');
32
33
    /**
34
     * Controls whether the server is going to echo debugging messages back to the client as comments in response body.
35
     * Valid values: 0,1,2,3
36
     */
37
    public $debug = 1;
38
39
    /**
40
     * Controls behaviour of server when the invoked user function throws an exception:
41
     * 0 = catch it and return an 'internal error' xmlrpc response (default)
42
     * 1 = catch it and return an xmlrpc response with the error corresponding to the exception
43
     * 2 = allow the exception to float to the upper layers
44
     */
45
    public $exception_handling = 0;
46
47
    /**
48
     * When set to true, it will enable HTTP compression of the response, in case
49
     * the client has declared its support for compression in the request.
50
     * Set at constructor time.
51
     */
52
    public $compress_response = false;
53
54
    /**
55
     * List of http compression methods accepted by the server for requests. Set at constructor time.
56
     * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib
57
     */
58
    public $accepted_compression = array();
59
60
    /// Shall we serve calls to system.* methods?
61
    public $allow_system_funcs = true;
62
63
    /**
64
     * List of charset encodings natively accepted for requests.
65
     * Set at constructor time.
66
     * UNUSED so far...
67
     */
68
    public $accepted_charset_encodings = array();
69
70
    /**
71
     * Charset encoding to be used for response.
72
     * NB: if we can, we will convert the generated response from internal_encoding to the intended one.
73
     * Can be: a supported xml encoding (only UTF-8 and ISO-8859-1 at present, unless mbstring is enabled),
74
     * null (leave unspecified in response, convert output stream to US_ASCII),
75
     * 'default' (use xmlrpc library default as specified in xmlrpc.inc, convert output stream if needed),
76
     * 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).
77
     * NB: pretty dangerous if you accept every charset and do not have mbstring enabled)
78
     */
79
    public $response_charset_encoding = '';
80
81
    /**
82
     * Storage for internal debug info.
83
     */
84
    protected $debug_info = '';
85
86
    /**
87
     * Extra data passed at runtime to method handling functions. Used only by EPI layer
88
     */
89
    public $user_data = null;
90
91
    protected static $_xmlrpc_debuginfo = '';
92
    protected static $_xmlrpcs_occurred_errors = '';
93
    protected static $_xmlrpcs_prev_ehandler = '';
94
95
    /**
96
     * @param array $dispatchMap the dispatch map with definition of exposed services
97
     * @param boolean $serviceNow set to false to prevent the server from running upon construction
98
     */
99 489
    public function __construct($dispatchMap = null, $serviceNow = true)
100
    {
101
        // if ZLIB is enabled, let the server by default accept compressed requests,
102
        // and compress responses sent to clients that support them
103 489
        if (function_exists('gzinflate')) {
104 489
            $this->accepted_compression = array('gzip', 'deflate');
105 489
            $this->compress_response = true;
106
        }
107
108
        // by default the xml parser can support these 3 charset encodings
109 489
        $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII');
110
111
        // dispMap is a dispatch array of methods mapped to function names and signatures.
112
        // If a method doesn't appear in the map then an unknown method error is generated
113
        /* milosch - changed to make passing dispMap optional.
114
        * instead, you can use the class add_to_map() function
115
        * to add functions manually (borrowed from SOAPX4)
116
        */
117 489
        if ($dispatchMap) {
118 488
            $this->dmap = $dispatchMap;
119 488
            if ($serviceNow) {
120
                $this->service();
121
            }
122
        }
123 489
    }
124
125
    /**
126
     * Set debug level of server.
127
     *
128
     * @param integer $level debug lvl: determines info added to xmlrpc responses (as xml comments)
129
     *                    0 = no debug info,
130
     *                    1 = msgs set from user with debugmsg(),
131
     *                    2 = add complete xmlrpc request (headers and body),
132
     *                    3 = add also all processing warnings happened during method processing
133
     *                    (NB: this involves setting a custom error handler, and might interfere
134
     *                    with the standard processing of the php function exposed as method. In
135
     *                    particular, triggering an USER_ERROR level error will not halt script
136
     *                    execution anymore, but just end up logged in the xmlrpc response)
137
     *                    Note that info added at level 2 and 3 will be base64 encoded
138
     */
139 488
    public function setDebug($level)
140
    {
141 488
        $this->debug = $level;
142 488
    }
143
144
    /**
145
     * Add a string to the debug info that can be later serialized by the server
146
     * as part of the response message.
147
     * Note that for best compatibility, the debug string should be encoded using
148
     * the PhpXmlRpc::$xmlrpc_internalencoding character set.
149
     *
150
     * @param string $msg
151
     * @access public
152
     */
153 2
    public static function xmlrpc_debugmsg($msg)
154
    {
155 2
        static::$_xmlrpc_debuginfo .= $msg . "\n";
156 2
    }
157
158
    /**
159
     * @param string $msg
160
     */
161 20
    public static function error_occurred($msg)
162
    {
163 20
        static::$_xmlrpcs_occurred_errors .= $msg . "\n";
164 20
    }
165
166
    /**
167
     * Return a string with the serialized representation of all debug info.
168
     *
169
     * @param string $charsetEncoding the target charset encoding for the serialization
170
     *
171
     * @return string an XML comment (or two)
172
     */
173 488
    public function serializeDebug($charsetEncoding = '')
174
    {
175
        // Tough encoding problem: which internal charset should we assume for debug info?
176
        // It might contain a copy of raw data received from client, ie with unknown encoding,
177
        // intermixed with php generated data and user generated data...
178
        // so we split it: system debug is base 64 encoded,
179
        // user debug info should be encoded by the end user using the INTERNAL_ENCODING
180 488
        $out = '';
181 488
        if ($this->debug_info != '') {
182 488
            $out .= "<!-- SERVER DEBUG INFO (BASE64 ENCODED):\n" . base64_encode($this->debug_info) . "\n-->\n";
183
        }
184 488
        if (static::$_xmlrpc_debuginfo != '') {
185 2
            $out .= "<!-- DEBUG INFO:\n" . Charset::instance()->encodeEntities(str_replace('--', '_-', static::$_xmlrpc_debuginfo), PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "\n-->\n";
186
            // NB: a better solution MIGHT be to use CDATA, but we need to insert it
187
            // into return payload AFTER the beginning tag
188
            //$out .= "<![CDATA[ DEBUG INFO:\n\n" . str_replace(']]>', ']_]_>', static::$_xmlrpc_debuginfo) . "\n]]>\n";
189
        }
190
191 488
        return $out;
192
    }
193
194
    /**
195
     * Execute the xmlrpc request, printing the response.
196
     *
197
     * @param string $data the request body. If null, the http POST request will be examined
198
     * @param bool $returnPayload When true, return the response but do not echo it or any http header
199
     *
200
     * @return Response|string the response object (usually not used by caller...) or its xml serialization
201
     *
202
     * @throws \Exception in case the executed method does throw an exception (and depending on server configuration)
203
     */
204 488
    public function service($data = null, $returnPayload = false)
205
    {
206 488
        if ($data === null) {
207 488
            $data = file_get_contents('php://input');
208
        }
209 488
        $rawData = $data;
210
211
        // reset internal debug info
212 488
        $this->debug_info = '';
213
214
        // Save what we received, before parsing it
215 488
        if ($this->debug > 1) {
216 488
            $this->debugmsg("+++GOT+++\n" . $data . "\n+++END+++");
217
        }
218
219 488
        $r = $this->parseRequestHeaders($data, $reqCharset, $respCharset, $respEncoding);
220 488
        if (!$r) {
221
            // this actually executes the request
222 488
            $r = $this->parseRequest($data, $reqCharset);
223
        }
224
225
        // save full body of request into response, for more debugging usages
226 488
        $r->raw_data = $rawData;
227
228 488
        if ($this->debug > 2 && static::$_xmlrpcs_occurred_errors) {
229 20
            $this->debugmsg("+++PROCESSING ERRORS AND WARNINGS+++\n" .
230 20
                static::$_xmlrpcs_occurred_errors . "+++END+++");
231
        }
232
233 488
        $payload = $this->xml_header($respCharset);
234 488
        if ($this->debug > 0) {
235 488
            $payload = $payload . $this->serializeDebug($respCharset);
236
        }
237
238
        // G. Giunta 2006-01-27: do not create response serialization if it has
239
        // already happened. Helps building json magic
240 488
        if (empty($r->payload)) {
241 488
            $r->serialize($respCharset);
242
        }
243 488
        $payload = $payload . $r->payload;
244
245 488
        if ($returnPayload) {
246
            return $payload;
247
        }
248
249
        // if we get a warning/error that has output some text before here, then we cannot
250
        // add a new header. We cannot say we are sending xml, either...
251 488
        if (!headers_sent()) {
252 488
            header('Content-Type: ' . $r->content_type);
253
            // we do not know if client actually told us an accepted charset, but if he did
254
            // we have to tell him what we did
255 488
            header("Vary: Accept-Charset");
256
257
            // http compression of output: only
258
            // if we can do it, and we want to do it, and client asked us to,
259
            // and php ini settings do not force it already
260 488
            $phpNoSelfCompress = !ini_get('zlib.output_compression') && (ini_get('output_handler') != 'ob_gzhandler');
261 488
            if ($this->compress_response && function_exists('gzencode') && $respEncoding != ''
262 100
                && $phpNoSelfCompress
263
            ) {
264 100
                if (strpos($respEncoding, 'gzip') !== false) {
265 50
                    $payload = gzencode($payload);
266 50
                    header("Content-Encoding: gzip");
267 50
                    header("Vary: Accept-Encoding");
268 50
                } elseif (strpos($respEncoding, 'deflate') !== false) {
269 50
                    $payload = gzcompress($payload);
270 50
                    header("Content-Encoding: deflate");
271 50
                    header("Vary: Accept-Encoding");
272
                }
273
            }
274
275
            // do not output content-length header if php is compressing output for us:
276
            // it will mess up measurements
277 488
            if ($phpNoSelfCompress) {
278 488
                header('Content-Length: ' . (int)strlen($payload));
279
            }
280
        } else {
281
            error_log('XML-RPC: ' . __METHOD__ . ': http headers already sent before response is fully generated. Check for php warning or error messages');
282
        }
283
284 488
        print $payload;
285
286
        // return request, in case subclasses want it
287 488
        return $r;
288
    }
289
290
    /**
291
     * Add a method to the dispatch map.
292
     *
293
     * @param string $methodName the name with which the method will be made available
294
     * @param string $function the php function that will get invoked
295
     * @param array $sig the array of valid method signatures
296
     * @param string $doc method documentation
297
     * @param array $sigDoc the array of valid method signatures docs (one string per param, one for return type)
298
     */
299
    public function add_to_map($methodName, $function, $sig = null, $doc = false, $sigDoc = false)
300
    {
301
        $this->dmap[$methodName] = array(
302
            'function' => $function,
303
            'docstring' => $doc,
304
        );
305
        if ($sig) {
306
            $this->dmap[$methodName]['signature'] = $sig;
307
        }
308
        if ($sigDoc) {
309
            $this->dmap[$methodName]['signature_docs'] = $sigDoc;
310
        }
311
    }
312
313
    /**
314
     * Verify type and number of parameters received against a list of known signatures.
315
     *
316
     * @param array|Request $in array of either xmlrpc value objects or xmlrpc type definitions
317
     * @param array $sigs array of known signatures to match against
318
     *
319
     * @return array
320
     */
321 467
    protected function verifySignature($in, $sigs)
322
    {
323
        // check each possible signature in turn
324 467
        if (is_object($in)) {
325 467
            $numParams = $in->getNumParams();
326
        } else {
327
            $numParams = count($in);
328
        }
329 467
        foreach ($sigs as $curSig) {
330 467
            if (count($curSig) == $numParams + 1) {
331 467
                $itsOK = 1;
332 467
                for ($n = 0; $n < $numParams; $n++) {
333 448
                    if (is_object($in)) {
334 448
                        $p = $in->getParam($n);
335 448
                        if ($p->kindOf() == 'scalar') {
336 391
                            $pt = $p->scalartyp();
337
                        } else {
338 134
                            $pt = $p->kindOf();
339
                        }
340
                    } else {
341
                        $pt = ($in[$n] == 'i4') ? 'int' : strtolower($in[$n]); // dispatch maps never use i4...
342
                    }
343
344
                    // param index is $n+1, as first member of sig is return type
345 448
                    if ($pt != $curSig[$n + 1] && $curSig[$n + 1] != Value::$xmlrpcValue) {
346 20
                        $itsOK = 0;
347 20
                        $pno = $n + 1;
348 20
                        $wanted = $curSig[$n + 1];
349 20
                        $got = $pt;
350 20
                        break;
351
                    }
352
                }
353 467
                if ($itsOK) {
354 467
                    return array(1, '');
355
                }
356
            }
357
        }
358 20
        if (isset($wanted)) {
359
            return array(0, "Wanted ${wanted}, got ${got} at param ${pno}");
0 ignored issues
show
Bug introduced by
The variable $got does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $pno does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
360
        } else {
361 20
            return array(0, "No method signature matches number of parameters");
362
        }
363
    }
364
365
    /**
366
     * Parse http headers received along with xmlrpc request. If needed, inflate request.
367
     *
368
     * @return mixed Response|null on success or an error Response
369
     */
370
    protected function parseRequestHeaders(&$data, &$reqEncoding, &$respEncoding, &$respCompression)
371
    {
372
        // check if $_SERVER is populated: it might have been disabled via ini file
373
        // (this is true even when in CLI mode)
374 488
        if (count($_SERVER) == 0) {
375
            error_log('XML-RPC: ' . __METHOD__ . ': cannot parse request headers as $_SERVER is not populated');
376
        }
377
378 488
        if ($this->debug > 1) {
379 488
            if (function_exists('getallheaders')) {
380 488
                $this->debugmsg(''); // empty line
381 488
                foreach (getallheaders() as $name => $val) {
382 488
                    $this->debugmsg("HEADER: $name: $val");
383
                }
384
            }
385
        }
386
387 488
        if (isset($_SERVER['HTTP_CONTENT_ENCODING'])) {
388 100
            $contentEncoding = str_replace('x-', '', $_SERVER['HTTP_CONTENT_ENCODING']);
389
        } else {
390 388
            $contentEncoding = '';
391
        }
392
393
        // check if request body has been compressed and decompress it
394 488
        if ($contentEncoding != '' && strlen($data)) {
395 100
            if ($contentEncoding == 'deflate' || $contentEncoding == 'gzip') {
396
                // if decoding works, use it. else assume data wasn't gzencoded
397 100
                if (function_exists('gzinflate') && in_array($contentEncoding, $this->accepted_compression)) {
398 100
                    if ($contentEncoding == 'deflate' && $degzdata = @gzuncompress($data)) {
399 50
                        $data = $degzdata;
400 50 View Code Duplication
                        if ($this->debug > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
401 50
                            $this->debugmsg("\n+++INFLATED REQUEST+++[" . strlen($data) . " chars]+++\n" . $data . "\n+++END+++");
402
                        }
403 50
                    } elseif ($contentEncoding == 'gzip' && $degzdata = @gzinflate(substr($data, 10))) {
404 50
                        $data = $degzdata;
405 50 View Code Duplication
                        if ($this->debug > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
406 50
                            $this->debugmsg("+++INFLATED REQUEST+++[" . strlen($data) . " chars]+++\n" . $data . "\n+++END+++");
407
                        }
408
                    } else {
409
                        $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_decompress_fail'], PhpXmlRpc::$xmlrpcstr['server_decompress_fail']);
410
411
                        return $r;
412
                    }
413
                } else {
414
                    $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_cannot_decompress'], PhpXmlRpc::$xmlrpcstr['server_cannot_decompress']);
415
416
                    return $r;
417
                }
418
            }
419
        }
420
421
        // check if client specified accepted charsets, and if we know how to fulfill
422
        // the request
423 488
        if ($this->response_charset_encoding == 'auto') {
424
            $respEncoding = '';
425
            if (isset($_SERVER['HTTP_ACCEPT_CHARSET'])) {
426
                // here we should check if we can match the client-requested encoding
427
                // with the encodings we know we can generate.
428
                /// @todo we should parse q=0.x preferences instead of getting first charset specified...
429
                $clientAcceptedCharsets = explode(',', strtoupper($_SERVER['HTTP_ACCEPT_CHARSET']));
430
                // Give preference to internal encoding
431
                $knownCharsets = array(PhpXmlRpc::$xmlrpc_internalencoding, 'UTF-8', 'ISO-8859-1', 'US-ASCII');
432
                foreach ($knownCharsets as $charset) {
433
                    foreach ($clientAcceptedCharsets as $accepted) {
434
                        if (strpos($accepted, $charset) === 0) {
435
                            $respEncoding = $charset;
436
                            break;
437
                        }
438
                    }
439
                    if ($respEncoding) {
440
                        break;
441
                    }
442
                }
443
            }
444
        } else {
445 488
            $respEncoding = $this->response_charset_encoding;
446
        }
447
448 488
        if (isset($_SERVER['HTTP_ACCEPT_ENCODING'])) {
449 100
            $respCompression = $_SERVER['HTTP_ACCEPT_ENCODING'];
450
        } else {
451 388
            $respCompression = '';
452
        }
453
454
        // 'guestimate' request encoding
455
        /// @todo check if mbstring is enabled and automagic input conversion is on: it might mingle with this check???
456 488
        $reqEncoding = XMLParser::guessEncoding(isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : '',
457
            $data);
458
459 488
        return null;
460
    }
461
462
    /**
463
     * Parse an xml chunk containing an xmlrpc request and execute the corresponding
464
     * php function registered with the server.
465
     *
466
     * @param string $data the xml request
467
     * @param string $reqEncoding (optional) the charset encoding of the xml request
468
     *
469
     * @return Response
470
     *
471
     * @throws \Exception in case the executed method does throw an exception (and depending on server configuration)
472
     */
473
    public function parseRequest($data, $reqEncoding = '')
474
    {
475
        // decompose incoming XML into request structure
476
477 489 View Code Duplication
        if ($reqEncoding != '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
478
            // Since parsing will fail if charset is not specified in the xml prologue,
479
            // the encoding is not UTF8 and there are non-ascii chars in the text, we try to work round that...
480
            // The following code might be better for mb_string enabled installs, but
481
            // makes the lib about 200% slower...
482
            //if (!is_valid_charset($reqEncoding, array('UTF-8')))
483 488
            if (!in_array($reqEncoding, array('UTF-8', 'US-ASCII')) && !XMLParser::hasEncoding($data)) {
484 4
                if ($reqEncoding == 'ISO-8859-1') {
485 2
                    $data = utf8_encode($data);
486
                } else {
487 2
                    if (extension_loaded('mbstring')) {
488 2
                        $data = mb_convert_encoding($data, 'UTF-8', $reqEncoding);
489
                    } else {
490
                        error_log('XML-RPC: ' . __METHOD__ . ': invalid charset encoding of received request: ' . $reqEncoding);
491
                    }
492
                }
493
            }
494
        }
495
496
        // PHP internally might use ISO-8859-1, so we have to tell the xml parser to give us back data in the expected charset.
497
        // What if internal encoding is not in one of the 3 allowed? We use the broadest one, ie. utf8
498
        // This allows to send data which is native in various charset,
499
        // by extending xmlrpc_encode_entities() and setting xmlrpc_internalencoding
500 489 View Code Duplication
        if (!in_array(PhpXmlRpc::$xmlrpc_internalencoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
501
            $options = array(XML_OPTION_TARGET_ENCODING => 'UTF-8');
502
        } else {
503 489
            $options = array(XML_OPTION_TARGET_ENCODING => PhpXmlRpc::$xmlrpc_internalencoding);
504
        }
505
506 489
        $xmlRpcParser = new XMLParser($options);
507 489
        $xmlRpcParser->parse($data, $this->functions_parameters_type, XMLParser::ACCEPT_REQUEST);
508 489
        if ($xmlRpcParser->_xh['isf'] > 2) {
509
            // (BC) we return XML error as a faultCode
510
            preg_match('/XML error [0-9]+/', $xmlRpcParser->_xh['isf_reason'], $matches);
511
            $r = new Response(0,
512
                PhpXmlRpc::$xmlrpcerrxml + $matches[1],
513
                $xmlRpcParser->_xh['isf_reason']);
514 489 View Code Duplication
        } elseif ($xmlRpcParser->_xh['isf']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
515 1
            $r = new Response(0,
516 1
                PhpXmlRpc::$xmlrpcerr['invalid_request'],
517 1
                PhpXmlRpc::$xmlrpcstr['invalid_request'] . ' ' . $xmlRpcParser->_xh['isf_reason']);
518
        } else {
519
            // small layering violation in favor of speed and memory usage:
520
            // we should allow the 'execute' method handle this, but in the
521
            // most common scenario (xmlrpc values type server with some methods
522
            // registered as phpvals) that would mean a useless encode+decode pass
523 488
            if ($this->functions_parameters_type != 'xmlrpcvals' ||
524 488
                (isset($this->dmap[$xmlRpcParser->_xh['method']]['parameters_type']) &&
525
                    ($this->dmap[$xmlRpcParser->_xh['method']]['parameters_type'] == 'phpvals')
526
                )
527
            ) {
528
                if ($this->debug > 1) {
529
                    $this->debugmsg("\n+++PARSED+++\n" . var_export($xmlRpcParser->_xh['params'], true) . "\n+++END+++");
530
                }
531
                $r = $this->execute($xmlRpcParser->_xh['method'], $xmlRpcParser->_xh['params'], $xmlRpcParser->_xh['pt']);
532
            } else {
533
                // build a Request object with data parsed from xml
534 488
                $req = new Request($xmlRpcParser->_xh['method']);
535
                // now add parameters in
536 488 View Code Duplication
                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...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
537 469
                    $req->addParam($xmlRpcParser->_xh['params'][$i]);
538
                }
539
540 488
                if ($this->debug > 1) {
541 488
                    $this->debugmsg("\n+++PARSED+++\n" . var_export($req, true) . "\n+++END+++");
542
                }
543 488
                $r = $this->execute($req);
544
            }
545
        }
546
547 489
        return $r;
548
    }
549
550
    /**
551
     * Execute a method invoked by the client, checking parameters used.
552
     *
553
     * @param mixed $req either a Request obj or a method name
554
     * @param array $params array with method parameters as php types (if m is method name only)
555
     * @param array $paramTypes array with xmlrpc types of method parameters (if m is method name only)
556
     *
557
     * @return Response
558
     *
559
     * @throws \Exception in case the executed method does throw an exception (and depending on server configuration)
560
     */
561
    protected function execute($req, $params = null, $paramTypes = null)
562
    {
563 488
        static::$_xmlrpcs_occurred_errors = '';
564 488
        static::$_xmlrpc_debuginfo = '';
565
566 488
        if (is_object($req)) {
567 488
            $methName = $req->method();
568
        } else {
569
            $methName = $req;
570
        }
571 488
        $sysCall = $this->allow_system_funcs && (strpos($methName, "system.") === 0);
572 488
        $dmap = $sysCall ? $this->getSystemDispatchMap() : $this->dmap;
573
574 488 View Code Duplication
        if (!isset($dmap[$methName]['function'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
575
            // No such method
576 77
            return new Response(0,
577 77
                PhpXmlRpc::$xmlrpcerr['unknown_method'],
578 77
                PhpXmlRpc::$xmlrpcstr['unknown_method']);
579
        }
580
581
        // Check signature
582 488
        if (isset($dmap[$methName]['signature'])) {
583 467
            $sig = $dmap[$methName]['signature'];
584 467
            if (is_object($req)) {
585 467
                list($ok, $errStr) = $this->verifySignature($req, $sig);
586
            } else {
587
                list($ok, $errStr) = $this->verifySignature($paramTypes, $sig);
0 ignored issues
show
Bug introduced by
It seems like $paramTypes can also be of type null; however, PhpXmlRpc\Server::verifySignature() does only seem to accept array|object<PhpXmlRpc\Request>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
588
            }
589 467 View Code Duplication
            if (!$ok) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
590
                // Didn't match.
591 20
                return new Response(
592 20
                    0,
593 20
                    PhpXmlRpc::$xmlrpcerr['incorrect_params'],
594 20
                    PhpXmlRpc::$xmlrpcstr['incorrect_params'] . ": ${errStr}"
595
                );
596
            }
597
        }
598
599 488
        $func = $dmap[$methName]['function'];
600
        // let the 'class::function' syntax be accepted in dispatch maps
601 488
        if (is_string($func) && strpos($func, '::')) {
602 115
            $func = explode('::', $func);
603
        }
604
605 488
        if (is_array($func)) {
606 136 View Code Duplication
            if (is_object($func[0])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
607 22
                $funcName = get_class($func[0]) . '->' . $func[1];
608
            } else {
609 115
                $funcName = implode('::', $func);
610
            }
611 372
        } else if ($func instanceof \Closure) {
612 98
            $funcName = 'Closure';
613
        } else {
614 294
            $funcName = $func;
615
        }
616
617
        // verify that function to be invoked is in fact callable
618 488 View Code Duplication
        if (!is_callable($func)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
619
            error_log("XML-RPC: " . __METHOD__ . ": function '$funcName' registered as method handler is not callable");
620
            return new Response(
621
                0,
622
                PhpXmlRpc::$xmlrpcerr['server_error'],
623
                PhpXmlRpc::$xmlrpcstr['server_error'] . ": no function matches method"
624
            );
625
        }
626
627
        // If debug level is 3, we should catch all errors generated during
628
        // processing of user function, and log them as part of response
629 488
        if ($this->debug > 2) {
630 488
            self::$_xmlrpcs_prev_ehandler = set_error_handler(array('\PhpXmlRpc\Server', '_xmlrpcs_errorHandler'));
0 ignored issues
show
Documentation Bug introduced by
It seems like set_error_handler(array(...xmlrpcs_errorHandler')) can also be of type callable. However, the property $_xmlrpcs_prev_ehandler is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
631
        }
632
633
        try {
634
            // Allow mixed-convention servers
635 488
            if (is_object($req)) {
636 488
                if ($sysCall) {
637 115
                    $r = call_user_func($func, $this, $req);
638
                } else {
639 393
                    $r = call_user_func($func, $req);
640
                }
641 486
                if (!is_a($r, 'PhpXmlRpc\Response')) {
642
                    error_log("XML-RPC: " . __METHOD__ . ": function '$funcName' registered as method handler does not return an xmlrpc response object but a " . gettype($r));
643 View Code Duplication
                    if (is_a($r, 'PhpXmlRpc\Value')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
644
                        $r = new Response($r);
645
                    } else {
646
                        $r = new Response(
647
                            0,
648
                            PhpXmlRpc::$xmlrpcerr['server_error'],
649
                            PhpXmlRpc::$xmlrpcstr['server_error'] . ": function does not return xmlrpc response object"
650
                        );
651
                    }
652
                }
653
            } else {
654
                // call a 'plain php' function
655
                if ($sysCall) {
656
                    array_unshift($params, $this);
657
                    $r = call_user_func_array($func, $params);
658
                } else {
659
                    // 3rd API convention for method-handling functions: EPI-style
660
                    if ($this->functions_parameters_type == 'epivals') {
661
                        $r = call_user_func_array($func, array($methName, $params, $this->user_data));
662
                        // mimic EPI behaviour: if we get an array that looks like an error, make it
663
                        // an eror response
664
                        if (is_array($r) && array_key_exists('faultCode', $r) && array_key_exists('faultString', $r)) {
665
                            $r = new Response(0, (integer)$r['faultCode'], (string)$r['faultString']);
666
                        } else {
667
                            // functions using EPI api should NOT return resp objects,
668
                            // so make sure we encode the return type correctly
669
                            $encoder = new Encoder();
670
                            $r = new Response($encoder->encode($r, array('extension_api')));
671
                        }
672
                    } else {
673
                        $r = call_user_func_array($func, $params);
674
                    }
675
                }
676
                // the return type can be either a Response object or a plain php value...
677
                if (!is_a($r, '\PhpXmlRpc\Response')) {
678
                    // what should we assume here about automatic encoding of datetimes
679
                    // and php classes instances???
680
                    $encoder = new Encoder();
681
                    $r = new Response($encoder->encode($r, $this->phpvals_encoding_options));
682
                }
683
            }
684 41
        } catch (\Exception $e) {
685
            // (barring errors in the lib) an uncatched exception happened
686
            // in the called function, we wrap it in a proper error-response
687 41
            switch ($this->exception_handling) {
688 41
                case 2:
689
                    if ($this->debug > 2) {
690
                        if (self::$_xmlrpcs_prev_ehandler) {
691
                            set_error_handler(self::$_xmlrpcs_prev_ehandler);
692
                        } else {
693
                            restore_error_handler();
694
                        }
695
                    }
696
                    throw $e;
697
                    break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
698 41
                case 1:
699 2
                    $r = new Response(0, $e->getCode(), $e->getMessage());
700 2
                    break;
701
                default:
702 41
                    $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_error'], PhpXmlRpc::$xmlrpcstr['server_error']);
703
            }
704
        }
705 488
        if ($this->debug > 2) {
706
            // note: restore the error handler we found before calling the
707
            // user func, even if it has been changed inside the func itself
708 488
            if (self::$_xmlrpcs_prev_ehandler) {
709 58
                set_error_handler(self::$_xmlrpcs_prev_ehandler);
710
            } else {
711 431
                restore_error_handler();
712
            }
713
        }
714
715 488
        return $r;
716
    }
717
718
    /**
719
     * Add a string to the 'internal debug message' (separate from 'user debug message').
720
     *
721
     * @param string $string
722
     */
723
    protected function debugmsg($string)
724
    {
725 488
        $this->debug_info .= $string . "\n";
726 488
    }
727
728
    /**
729
     * @param string $charsetEncoding
730
     * @return string
731
     */
732
    protected function xml_header($charsetEncoding = '')
733
    {
734 488
        if ($charsetEncoding != '') {
735 50
            return "<?xml version=\"1.0\" encoding=\"$charsetEncoding\"?" . ">\n";
736
        } else {
737 438
            return "<?xml version=\"1.0\"?" . ">\n";
738
        }
739
    }
740
741
    /* Functions that implement system.XXX methods of xmlrpc servers */
742
743
    /**
744
     * @return array
745
     */
746
    public function getSystemDispatchMap()
747
    {
748
        return array(
749
            'system.listMethods' => array(
750 115
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_listMethods',
751
                // listMethods: signature was either a string, or nothing.
752
                // The useless string variant has been removed
753 115
                'signature' => array(array(Value::$xmlrpcArray)),
754 115
                'docstring' => 'This method lists all the methods that the XML-RPC server knows how to dispatch',
755
                'signature_docs' => array(array('list of method names')),
756
            ),
757
            'system.methodHelp' => array(
758 115
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_methodHelp',
759 115
                'signature' => array(array(Value::$xmlrpcString, Value::$xmlrpcString)),
760 115
                'docstring' => 'Returns help text if defined for the method passed, otherwise returns an empty string',
761
                'signature_docs' => array(array('method description', 'name of the method to be described')),
762
            ),
763
            'system.methodSignature' => array(
764 115
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_methodSignature',
765 115
                'signature' => array(array(Value::$xmlrpcArray, Value::$xmlrpcString)),
766 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)',
767
                'signature_docs' => array(array('list of known signatures, each sig being an array of xmlrpc type names', 'name of method to be described')),
768
            ),
769
            'system.multicall' => array(
770 115
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_multicall',
771 115
                'signature' => array(array(Value::$xmlrpcArray, Value::$xmlrpcArray)),
772 115
                'docstring' => 'Boxcar multiple RPC calls in one request. See http://www.xmlrpc.com/discuss/msgReader$1208 for details',
773
                '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"')),
774
            ),
775
            'system.getCapabilities' => array(
776 115
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_getCapabilities',
777 115
                'signature' => array(array(Value::$xmlrpcStruct)),
778 115
                'docstring' => 'This method lists all the capabilites that the XML-RPC server has: the (more or less standard) extensions to the xmlrpc spec that it adheres to',
779
                'signature_docs' => array(array('list of capabilities, described as structs with a version number and url for the spec')),
780
            ),
781
        );
782
    }
783
784
    /**
785
     * @return array
786
     */
787
    public function getCapabilities()
788
    {
789
        $outAr = array(
790
            // xmlrpc spec: always supported
791
            'xmlrpc' => array(
792
                'specUrl' => 'http://www.xmlrpc.com/spec',
793
                'specVersion' => 1
794
            ),
795
            // if we support system.xxx functions, we always support multicall, too...
796
            // Note that, as of 2006/09/17, the following URL does not respond anymore
797
            'system.multicall' => array(
798
                'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
799
                'specVersion' => 1
800
            ),
801
            // introspection: version 2! we support 'mixed', too
802
            'introspection' => array(
803
                'specUrl' => 'http://phpxmlrpc.sourceforge.net/doc-2/ch10.html',
804
                'specVersion' => 2,
805
            ),
806
        );
807
808
        // NIL extension
809
        if (PhpXmlRpc::$xmlrpc_null_extension) {
810
            $outAr['nil'] = array(
811
                'specUrl' => 'http://www.ontosys.com/xml-rpc/extensions.php',
812
                'specVersion' => 1
813
            );
814
        }
815
816
        return $outAr;
817
    }
818
819
    /**
820
     * @param Server $server
821
     * @param Request $req
822
     * @return Response
823
     */
824
    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.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
825
    {
826
        $encoder = new Encoder();
827
        return new Response($encoder->encode($server->getCapabilities()));
828
    }
829
830
    /**
831
     * @param Server $server
832
     * @param Request $req if called in plain php values mode, second param is missing
833
     * @return Response
834
     */
835
    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.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
836
    {
837 20
        $outAr = array();
838 20
        foreach ($server->dmap as $key => $val) {
839 20
            $outAr[] = new Value($key, 'string');
840
        }
841 20
        if ($server->allow_system_funcs) {
842 20
            foreach ($server->getSystemDispatchMap() as $key => $val) {
843 20
                $outAr[] = new Value($key, 'string');
844
            }
845
        }
846
847 20
        return new Response(new Value($outAr, 'array'));
0 ignored issues
show
Documentation introduced by
$outAr is of type array, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
848
    }
849
850
    /**
851
     * @param Server $server
852
     * @param Request $req
853
     * @return Response
854
     */
855
    public static function _xmlrpcs_methodSignature($server, $req)
856
    {
857
        // let accept as parameter both an xmlrpc value or string
858 96 View Code Duplication
        if (is_object($req)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
859 96
            $methName = $req->getParam(0);
860 96
            $methName = $methName->scalarval();
861
        } else {
862
            $methName = $req;
863
        }
864 96 View Code Duplication
        if (strpos($methName, "system.") === 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
865 77
            $dmap = $server->getSystemDispatchMap();
866
        } else {
867 20
            $dmap = $server->dmap;
868
        }
869 96
        if (isset($dmap[$methName])) {
870 96
            if (isset($dmap[$methName]['signature'])) {
871 96
                $sigs = array();
872 96
                foreach ($dmap[$methName]['signature'] as $inSig) {
873 96
                    $curSig = array();
874 96
                    foreach ($inSig as $sig) {
875 96
                        $curSig[] = new Value($sig, 'string');
876
                    }
877 96
                    $sigs[] = new Value($curSig, 'array');
0 ignored issues
show
Documentation introduced by
$curSig is of type array, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
878
                }
879 96
                $r = new Response(new Value($sigs, 'array'));
0 ignored issues
show
Documentation introduced by
$sigs is of type array, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
880
            } else {
881
                // NB: according to the official docs, we should be returning a
882
                // "none-array" here, which means not-an-array
883
                $r = new Response(new Value('undef', 'string'));
884
            }
885
        } else {
886
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['introspect_unknown'], PhpXmlRpc::$xmlrpcstr['introspect_unknown']);
887
        }
888
889 96
        return $r;
890
    }
891
892
    /**
893
     * @param Server $server
894
     * @param Request $req
895
     * @return Response
896
     */
897
    public static function _xmlrpcs_methodHelp($server, $req)
898
    {
899
        // let accept as parameter both an xmlrpc value or string
900 77 View Code Duplication
        if (is_object($req)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
901 77
            $methName = $req->getParam(0);
902 77
            $methName = $methName->scalarval();
903
        } else {
904
            $methName = $req;
905
        }
906 77 View Code Duplication
        if (strpos($methName, "system.") === 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
907 77
            $dmap = $server->getSystemDispatchMap();
908
        } else {
909 1
            $dmap = $server->dmap;
910
        }
911 77
        if (isset($dmap[$methName])) {
912 77
            if (isset($dmap[$methName]['docstring'])) {
913 77
                $r = new Response(new Value($dmap[$methName]['docstring']), 'string');
914
            } else {
915
                $r = new Response(new Value('', 'string'));
916
            }
917
        } else {
918
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['introspect_unknown'], PhpXmlRpc::$xmlrpcstr['introspect_unknown']);
919
        }
920
921 77
        return $r;
922
    }
923
924
    public static function _xmlrpcs_multicall_error($err)
925
    {
926 58
        if (is_string($err)) {
927 58
            $str = PhpXmlRpc::$xmlrpcstr["multicall_${err}"];
928 58
            $code = PhpXmlRpc::$xmlrpcerr["multicall_${err}"];
929
        } else {
930 58
            $code = $err->faultCode();
931 58
            $str = $err->faultString();
932
        }
933 58
        $struct = array();
934 58
        $struct['faultCode'] = new Value($code, 'int');
935 58
        $struct['faultString'] = new Value($str, 'string');
936
937 58
        return new Value($struct, 'struct');
0 ignored issues
show
Documentation introduced by
$struct is of type array<string,object<PhpX...ct<PhpXmlRpc\\Value>"}>, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
938
    }
939
940
    /**
941
     * @param Server $server
942
     * @param Value $call
943
     * @return Value
944
     */
945
    public static function _xmlrpcs_multicall_do_call($server, $call)
946
    {
947 58
        if ($call->kindOf() != 'struct') {
948
            return static::_xmlrpcs_multicall_error('notstruct');
949
        }
950 58
        $methName = @$call['methodName'];
951 58
        if (!$methName) {
952
            return static::_xmlrpcs_multicall_error('nomethod');
953
        }
954 58
        if ($methName->kindOf() != 'scalar' || $methName->scalartyp() != 'string') {
955
            return static::_xmlrpcs_multicall_error('notstring');
956
        }
957 58
        if ($methName->scalarval() == 'system.multicall') {
958 58
            return static::_xmlrpcs_multicall_error('recursion');
959
        }
960
961 58
        $params = @$call['params'];
962 58
        if (!$params) {
963
            return static::_xmlrpcs_multicall_error('noparams');
964
        }
965 58
        if ($params->kindOf() != 'array') {
966
            return static::_xmlrpcs_multicall_error('notarray');
967
        }
968
969 58
        $req = new Request($methName->scalarval());
970 58
        foreach($params as $i => $param) {
971 58
            if (!$req->addParam($param)) {
972
                $i++; // for error message, we count params from 1
973
                return static::_xmlrpcs_multicall_error(new Response(0,
974
                    PhpXmlRpc::$xmlrpcerr['incorrect_params'],
975
                    PhpXmlRpc::$xmlrpcstr['incorrect_params'] . ": probable xml error in param " . $i));
976
            }
977
        }
978
979 58
        $result = $server->execute($req);
980
981 58
        if ($result->faultCode() != 0) {
982 58
            return static::_xmlrpcs_multicall_error($result); // Method returned fault.
983
        }
984
985 58
        return new Value(array($result->value()), 'array');
0 ignored issues
show
Documentation introduced by
array($result->value()) is of type array<integer,integer,{"0":"integer"}>, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
986
    }
987
988
    /**
989
     * @param Server $server
990
     * @param Value $call
991
     * @return Value
992
     */
993
    public static function _xmlrpcs_multicall_do_call_phpvals($server, $call)
994
    {
995
        if (!is_array($call)) {
996
            return static::_xmlrpcs_multicall_error('notstruct');
997
        }
998
        if (!array_key_exists('methodName', $call)) {
999
            return static::_xmlrpcs_multicall_error('nomethod');
1000
        }
1001
        if (!is_string($call['methodName'])) {
1002
            return static::_xmlrpcs_multicall_error('notstring');
1003
        }
1004
        if ($call['methodName'] == 'system.multicall') {
1005
            return static::_xmlrpcs_multicall_error('recursion');
1006
        }
1007
        if (!array_key_exists('params', $call)) {
1008
            return static::_xmlrpcs_multicall_error('noparams');
1009
        }
1010
        if (!is_array($call['params'])) {
1011
            return static::_xmlrpcs_multicall_error('notarray');
1012
        }
1013
1014
        // this is a real dirty and simplistic hack, since we might have received a
1015
        // base64 or datetime values, but they will be listed as strings here...
1016
        $pt = array();
1017
        $wrapper = new Wrapper();
1018
        foreach ($call['params'] as $val) {
1019
            $pt[] = $wrapper->php2XmlrpcType(gettype($val));
1020
        }
1021
1022
        $result = $server->execute($call['methodName'], $call['params'], $pt);
1023
1024
        if ($result->faultCode() != 0) {
1025
            return static::_xmlrpcs_multicall_error($result); // Method returned fault.
1026
        }
1027
1028
        return new Value(array($result->value()), 'array');
0 ignored issues
show
Documentation introduced by
array($result->value()) is of type array<integer,integer,{"0":"integer"}>, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1029
    }
1030
1031
    /**
1032
     * @param Server $server
1033
     * @param Request|array $req
1034
     * @return Response
1035
     */
1036
    public static function _xmlrpcs_multicall($server, $req)
1037
    {
1038 77
        $result = array();
1039
        // let accept a plain list of php parameters, beside a single xmlrpc msg object
1040 77
        if (is_object($req)) {
1041 77
            $calls = $req->getParam(0);
1042 77
            foreach($calls as $call) {
1043 58
                $result[] = static::_xmlrpcs_multicall_do_call($server, $call);
1044
            }
1045
        } else {
1046
            $numCalls = count($req);
1047
            for ($i = 0; $i < $numCalls; $i++) {
1048
                $result[$i] = static::_xmlrpcs_multicall_do_call_phpvals($server, $req[$i]);
1049
            }
1050
        }
1051
1052 77
        return new Response(new Value($result, 'array'));
0 ignored issues
show
Documentation introduced by
$result is of type array, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1053
    }
1054
1055
    /**
1056
     * Error handler used to track errors that occur during server-side execution of PHP code.
1057
     * This allows to report back to the client whether an internal error has occurred or not
1058
     * using an xmlrpc response object, instead of letting the client deal with the html junk
1059
     * that a PHP execution error on the server generally entails.
1060
     *
1061
     * NB: in fact a user defined error handler can only handle WARNING, NOTICE and USER_* errors.
1062
     */
1063
    public static function _xmlrpcs_errorHandler($errCode, $errString, $filename = null, $lineNo = null, $context = null)
1064
    {
1065
        // obey the @ protocol
1066 39
        if (error_reporting() == 0) {
1067 20
            return;
1068
        }
1069
1070
        //if($errCode != E_NOTICE && $errCode != E_WARNING && $errCode != E_USER_NOTICE && $errCode != E_USER_WARNING)
1071 20
        if ($errCode != E_STRICT) {
1072 20
            \PhpXmlRpc\Server::error_occurred($errString);
1073
        }
1074
        // Try to avoid as much as possible disruption to the previous error handling
1075
        // mechanism in place
1076 20
        if (self::$_xmlrpcs_prev_ehandler == '') {
1077
            // The previous error handler was the default: all we should do is log error
1078
            // to the default error log (if level high enough)
1079 20
            if (ini_get('log_errors') && (intval(ini_get('error_reporting')) & $errCode)) {
1080 20
                error_log($errString);
1081
            }
1082
        } else {
1083
            // Pass control on to previous error handler, trying to avoid loops...
1084
            if (self::$_xmlrpcs_prev_ehandler != array('\PhpXmlRpc\Server', '_xmlrpcs_errorHandler')) {
1085
                if (is_array(self::$_xmlrpcs_prev_ehandler)) {
1086
                    // the following works both with static class methods and plain object methods as error handler
1087
                    call_user_func_array(self::$_xmlrpcs_prev_ehandler, array($errCode, $errString, $filename, $lineNo, $context));
1088
                } else {
1089
                    $method = self::$_xmlrpcs_prev_ehandler;
1090
                    $method($errCode, $errString, $filename, $lineNo, $context);
1091
                }
1092
            }
1093
        }
1094 20
    }
1095
}
1096