Completed
Push — master ( 247d97...94468e )
by Gaetano
05:26
created

Server::__construct()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 25
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4.1967

Importance

Changes 0
Metric Value
cc 4
eloc 9
nc 6
nop 2
dl 0
loc 25
ccs 10
cts 13
cp 0.7692
crap 4.1967
rs 8.5806
c 0
b 0
f 0
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 1
        }
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 20
    public static function error_occurred($msg)
159
    {
160 20
        static::$_xmlrpcs_occurred_errors .= $msg . "\n";
161 20
    }
162
163
    /**
164
     * Return a string with the serialized representation of all debug info.
165
     *
166
     * @param string $charsetEncoding the target charset encoding for the serialization
167
     *
168
     * @return string an XML comment (or two)
169
     */
170 488
    public function serializeDebug($charsetEncoding = '')
171
    {
172
        // Tough encoding problem: which internal charset should we assume for debug info?
173
        // It might contain a copy of raw data received from client, ie with unknown encoding,
174
        // intermixed with php generated data and user generated data...
175
        // so we split it: system debug is base 64 encoded,
176
        // user debug info should be encoded by the end user using the INTERNAL_ENCODING
177 488
        $out = '';
178 488
        if ($this->debug_info != '') {
179 488
            $out .= "<!-- SERVER DEBUG INFO (BASE64 ENCODED):\n" . base64_encode($this->debug_info) . "\n-->\n";
180
        }
181 488
        if (static::$_xmlrpc_debuginfo != '') {
182 2
            $out .= "<!-- DEBUG INFO:\n" . Charset::instance()->encodeEntities(str_replace('--', '_-', static::$_xmlrpc_debuginfo), PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "\n-->\n";
183
            // NB: a better solution MIGHT be to use CDATA, but we need to insert it
184
            // into return payload AFTER the beginning tag
185
            //$out .= "<![CDATA[ DEBUG INFO:\n\n" . str_replace(']]>', ']_]_>', static::$_xmlrpc_debuginfo) . "\n]]>\n";
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
186
        }
187
188 488
        return $out;
189
    }
190
191
    /**
192
     * Execute the xmlrpc request, printing the response.
193
     *
194
     * @param string $data the request body. If null, the http POST request will be examined
195
     * @param bool $returnPayload When true, return the response but do not echo it or any http header
196
     *
197
     * @return Response|string the response object (usually not used by caller...) or its xml serialization
198
     *
199
     * @throws \Exception in case the executed method does throw an exception (and depending on server configuration)
200
     */
201 488
    public function service($data = null, $returnPayload = false)
202
    {
203 488
        if ($data === null) {
204 488
            $data = file_get_contents('php://input');
205
        }
206 488
        $rawData = $data;
207
208
        // reset internal debug info
209 488
        $this->debug_info = '';
210
211
        // Save what we received, before parsing it
212 488
        if ($this->debug > 1) {
213 488
            $this->debugmsg("+++GOT+++\n" . $data . "\n+++END+++");
214
        }
215
216 488
        $r = $this->parseRequestHeaders($data, $reqCharset, $respCharset, $respEncoding);
217 488
        if (!$r) {
218
            // this actually executes the request
219 488
            $r = $this->parseRequest($data, $reqCharset);
220
        }
221
222
        // save full body of request into response, for more debugging usages
223 488
        $r->raw_data = $rawData;
224
225 488
        if ($this->debug > 2 && static::$_xmlrpcs_occurred_errors) {
226 20
            $this->debugmsg("+++PROCESSING ERRORS AND WARNINGS+++\n" .
227 20
                static::$_xmlrpcs_occurred_errors . "+++END+++");
228
        }
229
230 488
        $payload = $this->xml_header($respCharset);
231 488
        if ($this->debug > 0) {
232 488
            $payload = $payload . $this->serializeDebug($respCharset);
233
        }
234
235
        // G. Giunta 2006-01-27: do not create response serialization if it has
236
        // already happened. Helps building json magic
237 488
        if (empty($r->payload)) {
238 488
            $r->serialize($respCharset);
239
        }
240 488
        $payload = $payload . $r->payload;
241
242 488
        if ($returnPayload) {
243
            return $payload;
244
        }
245
246
        // if we get a warning/error that has output some text before here, then we cannot
247
        // add a new header. We cannot say we are sending xml, either...
248 488
        if (!headers_sent()) {
249 488
            header('Content-Type: ' . $r->content_type);
250
            // we do not know if client actually told us an accepted charset, but if he did
251
            // we have to tell him what we did
252 488
            header("Vary: Accept-Charset");
253
254
            // http compression of output: only
255
            // if we can do it, and we want to do it, and client asked us to,
256
            // and php ini settings do not force it already
257 488
            $phpNoSelfCompress = !ini_get('zlib.output_compression') && (ini_get('output_handler') != 'ob_gzhandler');
258 488
            if ($this->compress_response && function_exists('gzencode') && $respEncoding != ''
259 488
                && $phpNoSelfCompress
260 100
            ) {
261 100
                if (strpos($respEncoding, 'gzip') !== false) {
262 50
                    $payload = gzencode($payload);
263 50
                    header("Content-Encoding: gzip");
264 50
                    header("Vary: Accept-Encoding");
265 100
                } elseif (strpos($respEncoding, 'deflate') !== false) {
266 50
                    $payload = gzcompress($payload);
267 50
                    header("Content-Encoding: deflate");
268 50
                    header("Vary: Accept-Encoding");
269
                }
270
            }
271
272
            // do not output content-length header if php is compressing output for us:
273
            // it will mess up measurements
274 488
            if ($phpNoSelfCompress) {
275 488
                header('Content-Length: ' . (int)strlen($payload));
276 20
            }
277 488
        } else {
278 20
            error_log('XML-RPC: ' . __METHOD__ . ': http headers already sent before response is fully generated. Check for php warning or error messages');
279
        }
280
281 488
        print $payload;
282
283
        // return request, in case subclasses want it
284 488
        return $r;
285
    }
286
287
    /**
288
     * Add a method to the dispatch map.
289
     *
290
     * @param string $methodName the name with which the method will be made available
291
     * @param string $function the php function that will get invoked
292
     * @param array $sig the array of valid method signatures
293
     * @param string $doc method documentation
294
     * @param array $sigDoc the array of valid method signatures docs (one string per param, one for return type)
295
     */
296
    public function add_to_map($methodName, $function, $sig = null, $doc = false, $sigDoc = false)
297
    {
298
        $this->dmap[$methodName] = array(
299
            'function' => $function,
300
            'docstring' => $doc,
301
        );
302
        if ($sig) {
303
            $this->dmap[$methodName]['signature'] = $sig;
304
        }
305
        if ($sigDoc) {
306
            $this->dmap[$methodName]['signature_docs'] = $sigDoc;
307
        }
308
    }
309
310
    /**
311
     * Verify type and number of parameters received against a list of known signatures.
312
     *
313
     * @param array|Request $in array of either xmlrpc value objects or xmlrpc type definitions
314
     * @param array $sigs array of known signatures to match against
315
     *
316
     * @return array
317
     */
318 467
    protected function verifySignature($in, $sigs)
319
    {
320
        // check each possible signature in turn
321 467
        if (is_object($in)) {
322 467
            $numParams = $in->getNumParams();
323 467
        } else {
324
            $numParams = count($in);
325
        }
326 467
        foreach ($sigs as $curSig) {
327 467
            if (count($curSig) == $numParams + 1) {
328 467
                $itsOK = 1;
329 467
                for ($n = 0; $n < $numParams; $n++) {
330 448
                    if (is_object($in)) {
331 448
                        $p = $in->getParam($n);
332 448
                        if ($p->kindOf() == 'scalar') {
333 391
                            $pt = $p->scalartyp();
334 391
                        } else {
335 134
                            $pt = $p->kindOf();
336
                        }
337 134
                    } else {
338
                        $pt = ($in[$n] == 'i4') ? 'int' : strtolower($in[$n]); // dispatch maps never use i4...
339
                    }
340
341
                    // param index is $n+1, as first member of sig is return type
342 448
                    if ($pt != $curSig[$n + 1] && $curSig[$n + 1] != Value::$xmlrpcValue) {
343 20
                        $itsOK = 0;
344 20
                        $pno = $n + 1;
345 20
                        $wanted = $curSig[$n + 1];
346 20
                        $got = $pt;
347 20
                        break;
348
                    }
349
                }
350 467
                if ($itsOK) {
351 467
                    return array(1, '');
352
                }
353
            }
354 20
        }
355 20
        if (isset($wanted)) {
356
            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...
357
        } else {
358 20
            return array(0, "No method signature matches number of parameters");
359
        }
360
    }
361
362
    /**
363
     * Parse http headers received along with xmlrpc request. If needed, inflate request.
364
     *
365
     * @return mixed Response|null on success or an error Response
366
     */
367
    protected function parseRequestHeaders(&$data, &$reqEncoding, &$respEncoding, &$respCompression)
0 ignored issues
show
Coding Style introduced by
parseRequestHeaders uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
368
    {
369
        // check if $_SERVER is populated: it might have been disabled via ini file
370
        // (this is true even when in CLI mode)
371 488
        if (count($_SERVER) == 0) {
372
            error_log('XML-RPC: ' . __METHOD__ . ': cannot parse request headers as $_SERVER is not populated');
373
        }
374
375 488
        if ($this->debug > 1) {
376 488
            if (function_exists('getallheaders')) {
377
                $this->debugmsg(''); // empty line
378
                foreach (getallheaders() as $name => $val) {
379
                    $this->debugmsg("HEADER: $name: $val");
380
                }
381
            }
382
        }
383
384 488
        if (isset($_SERVER['HTTP_CONTENT_ENCODING'])) {
385 100
            $contentEncoding = str_replace('x-', '', $_SERVER['HTTP_CONTENT_ENCODING']);
386 100
        } else {
387 388
            $contentEncoding = '';
388
        }
389
390
        // check if request body has been compressed and decompress it
391 488
        if ($contentEncoding != '' && strlen($data)) {
392 100
            if ($contentEncoding == 'deflate' || $contentEncoding == 'gzip') {
393
                // if decoding works, use it. else assume data wasn't gzencoded
394 100
                if (function_exists('gzinflate') && in_array($contentEncoding, $this->accepted_compression)) {
395 100
                    if ($contentEncoding == 'deflate' && $degzdata = @gzuncompress($data)) {
396 50
                        $data = $degzdata;
397 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...
398 50
                            $this->debugmsg("\n+++INFLATED REQUEST+++[" . strlen($data) . " chars]+++\n" . $data . "\n+++END+++");
399
                        }
400 100
                    } elseif ($contentEncoding == 'gzip' && $degzdata = @gzinflate(substr($data, 10))) {
401 50
                        $data = $degzdata;
402 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...
403 50
                            $this->debugmsg("+++INFLATED REQUEST+++[" . strlen($data) . " chars]+++\n" . $data . "\n+++END+++");
404
                        }
405 50
                    } else {
406
                        $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_decompress_fail'], PhpXmlRpc::$xmlrpcstr['server_decompress_fail']);
407
408
                        return $r;
409
                    }
410
                } else {
411
                    $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_cannot_decompress'], PhpXmlRpc::$xmlrpcstr['server_cannot_decompress']);
412
413
                    return $r;
414
                }
415
            }
416
        }
417
418
        // check if client specified accepted charsets, and if we know how to fulfill
419
        // the request
420 488
        if ($this->response_charset_encoding == 'auto') {
421
            $respEncoding = '';
422
            if (isset($_SERVER['HTTP_ACCEPT_CHARSET'])) {
423
                // here we should check if we can match the client-requested encoding
424
                // with the encodings we know we can generate.
425
                /// @todo we should parse q=0.x preferences instead of getting first charset specified...
426
                $clientAcceptedCharsets = explode(',', strtoupper($_SERVER['HTTP_ACCEPT_CHARSET']));
427
                // Give preference to internal encoding
428
                $knownCharsets = array(PhpXmlRpc::$xmlrpc_internalencoding, 'UTF-8', 'ISO-8859-1', 'US-ASCII');
429
                foreach ($knownCharsets as $charset) {
430
                    foreach ($clientAcceptedCharsets as $accepted) {
431 79
                        if (strpos($accepted, $charset) === 0) {
432
                            $respEncoding = $charset;
433
                            break;
434
                        }
435
                    }
436
                    if ($respEncoding) {
437
                        break;
438
                    }
439
                }
440
            }
441
        } else {
442 488
            $respEncoding = $this->response_charset_encoding;
443
        }
444
445 488
        if (isset($_SERVER['HTTP_ACCEPT_ENCODING'])) {
446 100
            $respCompression = $_SERVER['HTTP_ACCEPT_ENCODING'];
447 100
        } else {
448 388
            $respCompression = '';
449
        }
450
451
        // 'guestimate' request encoding
452
        /// @todo check if mbstring is enabled and automagic input conversion is on: it might mingle with this check???
453 488
        $reqEncoding = XMLParser::guessEncoding(isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : '',
454 488
            $data);
455
456 488
        return;
457
    }
458
459
    /**
460
     * Parse an xml chunk containing an xmlrpc request and execute the corresponding
461
     * php function registered with the server.
462
     *
463
     * @param string $data the xml request
464
     * @param string $reqEncoding (optional) the charset encoding of the xml request
465
     *
466
     * @return Response
467
     *
468
     * @throws \Exception in case the executed method does throw an exception (and depending on server configuration)
469
     */
470
    public function parseRequest($data, $reqEncoding = '')
471
    {
472
        // decompose incoming XML into request structure
473
474 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...
475
            // Since parsing will fail if charset is not specified in the xml prologue,
476
            // the encoding is not UTF8 and there are non-ascii chars in the text, we try to work round that...
477
            // The following code might be better for mb_string enabled installs, but
478
            // makes the lib about 200% slower...
479
            //if (!is_valid_charset($reqEncoding, array('UTF-8')))
0 ignored issues
show
Unused Code Comprehensibility introduced by
80% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
480 488
            if (!in_array($reqEncoding, array('UTF-8', 'US-ASCII')) && !XMLParser::hasEncoding($data)) {
481 4
                if ($reqEncoding == 'ISO-8859-1') {
482 2
                    $data = utf8_encode($data);
483 2
                } else {
484 2
                    if (extension_loaded('mbstring')) {
485 2
                        $data = mb_convert_encoding($data, 'UTF-8', $reqEncoding);
486 2
                    } else {
487
                        error_log('XML-RPC: ' . __METHOD__ . ': invalid charset encoding of received request: ' . $reqEncoding);
488
                    }
489
                }
490
            }
491
        }
492
493 489
        $parser = xml_parser_create();
494 489
        xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
495
        // G. Giunta 2005/02/13: PHP internally uses ISO-8859-1, so we have to tell
496
        // 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?
498
        // we use the broadest one, ie. utf8
499
        // This allows to send data which is native in various charset,
500
        // by extending xmlrpc_encode_entities() and setting xmlrpc_internalencoding
501 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...
502
            xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
503
        } else {
504 489
            xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, PhpXmlRpc::$xmlrpc_internalencoding);
505
        }
506
507 489
        $xmlRpcParser = new XMLParser();
508 489
        xml_set_object($parser, $xmlRpcParser);
509
510 489
        if ($this->functions_parameters_type != 'xmlrpcvals') {
511
            xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast');
512
        } else {
513 489
            xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee');
514
        }
515 489
        xml_set_character_data_handler($parser, 'xmlrpc_cd');
516 489
        xml_set_default_handler($parser, 'xmlrpc_dh');
517 489
        if (!xml_parse($parser, $data, 1)) {
518
            // return XML error as a faultCode
519
            $r = new Response(0,
520
                PhpXmlRpc::$xmlrpcerrxml + xml_get_error_code($parser),
521
                sprintf('XML error: %s at line %d, column %d',
522
                    xml_error_string(xml_get_error_code($parser)),
523
                    xml_get_current_line_number($parser), xml_get_current_column_number($parser)));
524
            xml_parser_free($parser);
525 489
        } elseif ($xmlRpcParser->_xh['isf']) {
526 1
            xml_parser_free($parser);
527 1
            $r = new Response(0,
528 1
                PhpXmlRpc::$xmlrpcerr['invalid_request'],
529 1
                PhpXmlRpc::$xmlrpcstr['invalid_request'] . ' ' . $xmlRpcParser->_xh['isf_reason']);
530 1
        } else {
531 488
            xml_parser_free($parser);
532
            // small layering violation in favor of speed and memory usage:
533
            // we should allow the 'execute' method handle this, but in the
534
            // most common scenario (xmlrpc values type server with some methods
535
            // registered as phpvals) that would mean a useless encode+decode pass
536 488
            if ($this->functions_parameters_type != 'xmlrpcvals' || (isset($this->dmap[$xmlRpcParser->_xh['method']]['parameters_type']) && ($this->dmap[$xmlRpcParser->_xh['method']]['parameters_type'] == 'phpvals'))) {
537
                if ($this->debug > 1) {
538
                    $this->debugmsg("\n+++PARSED+++\n" . var_export($xmlRpcParser->_xh['params'], true) . "\n+++END+++");
539
                }
540
                $r = $this->execute($xmlRpcParser->_xh['method'], $xmlRpcParser->_xh['params'], $xmlRpcParser->_xh['pt']);
541
            } else {
542
                // build a Request object with data parsed from xml
543 488
                $req = new Request($xmlRpcParser->_xh['method']);
544
                // now add parameters in
545 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...
546 469
                    $req->addParam($xmlRpcParser->_xh['params'][$i]);
547 469
                }
548
549 488
                if ($this->debug > 1) {
550 488
                    $this->debugmsg("\n+++PARSED+++\n" . var_export($req, true) . "\n+++END+++");
551
                }
552 488
                $r = $this->execute($req);
553
            }
554
        }
555
556 489
        return $r;
557
    }
558
559
    /**
560
     * Execute a method invoked by the client, checking parameters used.
561
     *
562
     * @param mixed $req either a Request obj or a method name
563
     * @param array $params array with method parameters as php types (if m is method name only)
564
     * @param array $paramTypes array with xmlrpc types of method parameters (if m is method name only)
565
     *
566
     * @return Response
567
     *
568
     * @throws \Exception in case the executed method does throw an exception (and depending on server configuration)
569
     */
570
    protected function execute($req, $params = null, $paramTypes = null)
571
    {
572 488
        static::$_xmlrpcs_occurred_errors = '';
573 488
        static::$_xmlrpc_debuginfo = '';
574
575 488
        if (is_object($req)) {
576 488
            $methName = $req->method();
577 488
        } else {
578
            $methName = $req;
579
        }
580 488
        $sysCall = $this->allow_system_funcs && (strpos($methName, "system.") === 0);
581 488
        $dmap = $sysCall ? $this->getSystemDispatchMap() : $this->dmap;
582
583 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...
584
            // No such method
585 77
            return new Response(0,
586 77
                PhpXmlRpc::$xmlrpcerr['unknown_method'],
587 77
                PhpXmlRpc::$xmlrpcstr['unknown_method']);
588
        }
589
590
        // Check signature
591 488
        if (isset($dmap[$methName]['signature'])) {
592 467
            $sig = $dmap[$methName]['signature'];
593 467
            if (is_object($req)) {
594 467
                list($ok, $errStr) = $this->verifySignature($req, $sig);
595 467
            } else {
596
                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...
597
            }
598 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...
599
                // Didn't match.
600 20
                return new Response(
601 20
                    0,
602 20
                    PhpXmlRpc::$xmlrpcerr['incorrect_params'],
603 20
                    PhpXmlRpc::$xmlrpcstr['incorrect_params'] . ": ${errStr}"
604 20
                );
605
            }
606
        }
607
608 488
        $func = $dmap[$methName]['function'];
609
        // let the 'class::function' syntax be accepted in dispatch maps
610 488
        if (is_string($func) && strpos($func, '::')) {
611 115
            $func = explode('::', $func);
612
        }
613
614 488
        if (is_array($func)) {
615 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...
616 22
                $funcName = get_class($func[0]) . '->' . $func[1];
617 22
            } else {
618 115
                $funcName = implode('::', $func);
619
            }
620 467
        } else if ($func instanceof \Closure) {
621 98
            $funcName = 'Closure';
622 98
        } else {
623 294
            $funcName = $func;
624
        }
625
626
        // verify that function to be invoked is in fact callable
627 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...
628
            error_log("XML-RPC: " . __METHOD__ . ": function '$funcName' registered as method handler is not callable");
629
            return new Response(
630
                0,
631
                PhpXmlRpc::$xmlrpcerr['server_error'],
632
                PhpXmlRpc::$xmlrpcstr['server_error'] . ": no function matches method"
633
            );
634
        }
635
636
        // If debug level is 3, we should catch all errors generated during
637
        // processing of user function, and log them as part of response
638 488
        if ($this->debug > 2) {
639 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...
640
        }
641
642
        try {
643
            // Allow mixed-convention servers
644 488
            if (is_object($req)) {
645 488
                if ($sysCall) {
646 115
                    $r = call_user_func($func, $this, $req);
647 115
                } else {
648 393
                    $r = call_user_func($func, $req);
649
                }
650 486
                if (!is_a($r, 'PhpXmlRpc\Response')) {
651
                    error_log("XML-RPC: " . __METHOD__ . ": function '$funcName' registered as method handler does not return an xmlrpc response object but a " . gettype($r));
652 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...
653
                        $r = new Response($r);
654
                    } else {
655
                        $r = new Response(
656
                            0,
657
                            PhpXmlRpc::$xmlrpcerr['server_error'],
658
                            PhpXmlRpc::$xmlrpcstr['server_error'] . ": function does not return xmlrpc response object"
659
                        );
660
                    }
661
                }
662
            } else {
663
                // call a 'plain php' function
664
                if ($sysCall) {
665
                    array_unshift($params, $this);
666
                    $r = call_user_func_array($func, $params);
667
                } else {
668
                    // 3rd API convention for method-handling functions: EPI-style
669
                    if ($this->functions_parameters_type == 'epivals') {
670
                        $r = call_user_func_array($func, array($methName, $params, $this->user_data));
671
                        // mimic EPI behaviour: if we get an array that looks like an error, make it
672
                        // an eror response
673
                        if (is_array($r) && array_key_exists('faultCode', $r) && array_key_exists('faultString', $r)) {
674
                            $r = new Response(0, (integer)$r['faultCode'], (string)$r['faultString']);
675
                        } else {
676
                            // functions using EPI api should NOT return resp objects,
677
                            // so make sure we encode the return type correctly
678
                            $encoder = new Encoder();
679
                            $r = new Response($encoder->encode($r, array('extension_api')));
680
                        }
681
                    } else {
682
                        $r = call_user_func_array($func, $params);
683
                    }
684
                }
685
                // the return type can be either a Response object or a plain php value...
686
                if (!is_a($r, '\PhpXmlRpc\Response')) {
687
                    // what should we assume here about automatic encoding of datetimes
688
                    // and php classes instances???
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
689
                    $encoder = new Encoder();
690
                    $r = new Response($encoder->encode($r, $this->phpvals_encoding_options));
691
                }
692
            }
693 41
        } catch (\Exception $e) {
694
            // (barring errors in the lib) an uncatched exception happened
695
            // in the called function, we wrap it in a proper error-response
696 41
            switch ($this->exception_handling) {
697 41
                case 2:
698
                    if ($this->debug > 2) {
699
                        if (self::$_xmlrpcs_prev_ehandler) {
700
                            set_error_handler(self::$_xmlrpcs_prev_ehandler);
701
                        } else {
702
                            restore_error_handler();
703
                        }
704
                    }
705
                    throw $e;
706
                    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...
707 41
                case 1:
708 2
                    $r = new Response(0, $e->getCode(), $e->getMessage());
709 2
                    break;
710 41
                default:
711 41
                    $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_error'], PhpXmlRpc::$xmlrpcstr['server_error']);
712 41
            }
713
        }
714 488
        if ($this->debug > 2) {
715
            // note: restore the error handler we found before calling the
716
            // user func, even if it has been changed inside the func itself
717 488
            if (self::$_xmlrpcs_prev_ehandler) {
718 58
                set_error_handler(self::$_xmlrpcs_prev_ehandler);
719 58
            } else {
720 431
                restore_error_handler();
721
            }
722
        }
723
724 488
        return $r;
725
    }
726
727
    /**
728
     * Add a string to the 'internal debug message' (separate from 'user debug message').
729
     *
730
     * @param string $string
731
     */
732
    protected function debugmsg($string)
733
    {
734 488
        $this->debug_info .= $string . "\n";
735 488
    }
736
737
    /**
738
     * @param string $charsetEncoding
739
     * @return string
740
     */
741
    protected function xml_header($charsetEncoding = '')
742
    {
743 488
        if ($charsetEncoding != '') {
744 50
            return "<?xml version=\"1.0\" encoding=\"$charsetEncoding\"?" . ">\n";
745
        } else {
746 438
            return "<?xml version=\"1.0\"?" . ">\n";
747
        }
748
    }
749
750
    /* Functions that implement system.XXX methods of xmlrpc servers */
751
752
    /**
753
     * @return array
754
     */
755
    public function getSystemDispatchMap()
756
    {
757
        return array(
758
            'system.listMethods' => array(
759 115
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_listMethods',
760
                // listMethods: signature was either a string, or nothing.
761
                // The useless string variant has been removed
762 115
                'signature' => array(array(Value::$xmlrpcArray)),
763 115
                'docstring' => 'This method lists all the methods that the XML-RPC server knows how to dispatch',
764 115
                'signature_docs' => array(array('list of method names')),
765 115
            ),
766
            'system.methodHelp' => array(
767 115
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_methodHelp',
768 115
                'signature' => array(array(Value::$xmlrpcString, Value::$xmlrpcString)),
769 115
                'docstring' => 'Returns help text if defined for the method passed, otherwise returns an empty string',
770 115
                'signature_docs' => array(array('method description', 'name of the method to be described')),
771 115
            ),
772
            'system.methodSignature' => array(
773 115
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_methodSignature',
774 115
                'signature' => array(array(Value::$xmlrpcArray, Value::$xmlrpcString)),
775 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)',
776 115
                'signature_docs' => array(array('list of known signatures, each sig being an array of xmlrpc type names', 'name of method to be described')),
777 115
            ),
778
            'system.multicall' => array(
779 115
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_multicall',
780 115
                'signature' => array(array(Value::$xmlrpcArray, Value::$xmlrpcArray)),
781 115
                'docstring' => 'Boxcar multiple RPC calls in one request. See http://www.xmlrpc.com/discuss/msgReader$1208 for details',
782 115
                '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"')),
783 115
            ),
784
            'system.getCapabilities' => array(
785 115
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_getCapabilities',
786 115
                'signature' => array(array(Value::$xmlrpcStruct)),
787 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',
788 115
                'signature_docs' => array(array('list of capabilities, described as structs with a version number and url for the spec')),
789 115
            ),
790 115
        );
791
    }
792
793
    /**
794
     * @return array
795
     */
796
    public function getCapabilities()
797
    {
798
        $outAr = array(
799
            // xmlrpc spec: always supported
800
            'xmlrpc' => array(
801
                'specUrl' => 'http://www.xmlrpc.com/spec',
802
                'specVersion' => 1
803
            ),
804
            // if we support system.xxx functions, we always support multicall, too...
805
            // Note that, as of 2006/09/17, the following URL does not respond anymore
806
            'system.multicall' => array(
807
                'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
808
                'specVersion' => 1
809
            ),
810
            // introspection: version 2! we support 'mixed', too
811
            'introspection' => array(
812
                'specUrl' => 'http://phpxmlrpc.sourceforge.net/doc-2/ch10.html',
813
                'specVersion' => 2,
814
            ),
815
        );
816
817
        // NIL extension
818
        if (PhpXmlRpc::$xmlrpc_null_extension) {
819
            $outAr['nil'] = array(
820
                'specUrl' => 'http://www.ontosys.com/xml-rpc/extensions.php',
821
                'specVersion' => 1
822
            );
823
        }
824
825
        return $outAr;
826
    }
827
828
    /**
829
     * @param Server $server
830
     * @param Request $req
831
     * @return Response
832
     */
833
    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...
834
    {
835
        $encoder = new Encoder();
836
        return new Response($encoder->encode($server->getCapabilities()));
837
    }
838
839
    /**
840
     * @param Server $server
841
     * @param Request $req
842
     * @return Response
843
     */
844
    public static function _xmlrpcs_listMethods($server, $req = null) // if called in plain php values mode, second param is missing
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...
845
    {
846 20
        $outAr = array();
847 20
        foreach ($server->dmap as $key => $val) {
848 20
            $outAr[] = new Value($key, 'string');
849 20
        }
850 20
        if ($server->allow_system_funcs) {
851 20
            foreach ($server->getSystemDispatchMap() as $key => $val) {
852 20
                $outAr[] = new Value($key, 'string');
853 20
            }
854
        }
855
856 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...
857
    }
858
859
    /**
860
     * @param Server $server
861
     * @param Request $req
862
     * @return Response
863
     */
864
    public static function _xmlrpcs_methodSignature($server, $req)
865
    {
866
        // let accept as parameter both an xmlrpc value or string
867 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...
868 96
            $methName = $req->getParam(0);
869 96
            $methName = $methName->scalarval();
870 96
        } else {
871
            $methName = $req;
872
        }
873 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...
874 77
            $dmap = $server->getSystemDispatchMap();
875 77
        } else {
876 20
            $dmap = $server->dmap;
877
        }
878 96
        if (isset($dmap[$methName])) {
879 96
            if (isset($dmap[$methName]['signature'])) {
880 96
                $sigs = array();
881 96
                foreach ($dmap[$methName]['signature'] as $inSig) {
882 96
                    $curSig = array();
883 96
                    foreach ($inSig as $sig) {
884 96
                        $curSig[] = new Value($sig, 'string');
885 96
                    }
886 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...
887 96
                }
888 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...
889 96
            } else {
890
                // NB: according to the official docs, we should be returning a
891
                // "none-array" here, which means not-an-array
892
                $r = new Response(new Value('undef', 'string'));
893
            }
894
        } else {
895
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['introspect_unknown'], PhpXmlRpc::$xmlrpcstr['introspect_unknown']);
896
        }
897
898 96
        return $r;
899
    }
900
901
    /**
902
     * @param Server $server
903
     * @param Request $req
904
     * @return Response
905
     */
906
    public static function _xmlrpcs_methodHelp($server, $req)
907
    {
908
        // let accept as parameter both an xmlrpc value or string
909 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...
910 77
            $methName = $req->getParam(0);
911 77
            $methName = $methName->scalarval();
912 77
        } else {
913
            $methName = $req;
914
        }
915 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...
916 77
            $dmap = $server->getSystemDispatchMap();
917 77
        } else {
918 1
            $dmap = $server->dmap;
919
        }
920 77
        if (isset($dmap[$methName])) {
921 77
            if (isset($dmap[$methName]['docstring'])) {
922 77
                $r = new Response(new Value($dmap[$methName]['docstring']), 'string');
923 77
            } else {
924
                $r = new Response(new Value('', 'string'));
925
            }
926
        } else {
927
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['introspect_unknown'], PhpXmlRpc::$xmlrpcstr['introspect_unknown']);
928
        }
929
930 77
        return $r;
931
    }
932
933
    public static function _xmlrpcs_multicall_error($err)
934
    {
935 58
        if (is_string($err)) {
936 58
            $str = PhpXmlRpc::$xmlrpcstr["multicall_${err}"];
937 58
            $code = PhpXmlRpc::$xmlrpcerr["multicall_${err}"];
938 58
        } else {
939 58
            $code = $err->faultCode();
940 58
            $str = $err->faultString();
941
        }
942 58
        $struct = array();
943 58
        $struct['faultCode'] = new Value($code, 'int');
944 58
        $struct['faultString'] = new Value($str, 'string');
945
946 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...
947
    }
948
949
    /**
950
     * @param Server $server
951
     * @param Value $call
952
     * @return Value
953
     */
954
    public static function _xmlrpcs_multicall_do_call($server, $call)
955
    {
956 58
        if ($call->kindOf() != 'struct') {
957
            return static::_xmlrpcs_multicall_error('notstruct');
958
        }
959 58
        $methName = @$call['methodName'];
960 58
        if (!$methName) {
961
            return static::_xmlrpcs_multicall_error('nomethod');
962
        }
963 58
        if ($methName->kindOf() != 'scalar' || $methName->scalartyp() != 'string') {
964
            return static::_xmlrpcs_multicall_error('notstring');
965
        }
966 58
        if ($methName->scalarval() == 'system.multicall') {
967 58
            return static::_xmlrpcs_multicall_error('recursion');
968
        }
969
970 58
        $params = @$call['params'];
971 58
        if (!$params) {
972
            return static::_xmlrpcs_multicall_error('noparams');
973
        }
974 58
        if ($params->kindOf() != 'array') {
975
            return static::_xmlrpcs_multicall_error('notarray');
976
        }
977
978 58
        $req = new Request($methName->scalarval());
979 58
        foreach($params as $i => $param) {
980 58
            if (!$req->addParam($param)) {
981
                $i++; // for error message, we count params from 1
982
                return static::_xmlrpcs_multicall_error(new Response(0,
983
                    PhpXmlRpc::$xmlrpcerr['incorrect_params'],
984
                    PhpXmlRpc::$xmlrpcstr['incorrect_params'] . ": probable xml error in param " . $i));
985
            }
986 58
        }
987
988 58
        $result = $server->execute($req);
989
990 58
        if ($result->faultCode() != 0) {
991 58
            return static::_xmlrpcs_multicall_error($result); // Method returned fault.
992
        }
993
994 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...
995
    }
996
997
    /**
998
     * @param Server $server
999
     * @param Value $call
1000
     * @return Value
1001
     */
1002
    public static function _xmlrpcs_multicall_do_call_phpvals($server, $call)
1003
    {
1004
        if (!is_array($call)) {
1005
            return static::_xmlrpcs_multicall_error('notstruct');
1006
        }
1007
        if (!array_key_exists('methodName', $call)) {
1008
            return static::_xmlrpcs_multicall_error('nomethod');
1009
        }
1010
        if (!is_string($call['methodName'])) {
1011
            return static::_xmlrpcs_multicall_error('notstring');
1012
        }
1013
        if ($call['methodName'] == 'system.multicall') {
1014
            return static::_xmlrpcs_multicall_error('recursion');
1015
        }
1016
        if (!array_key_exists('params', $call)) {
1017
            return static::_xmlrpcs_multicall_error('noparams');
1018
        }
1019
        if (!is_array($call['params'])) {
1020
            return static::_xmlrpcs_multicall_error('notarray');
1021
        }
1022
1023
        // this is a real dirty and simplistic hack, since we might have received a
1024
        // base64 or datetime values, but they will be listed as strings here...
1025
        $numParams = count($call['params']);
0 ignored issues
show
Unused Code introduced by
$numParams is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1026
        $pt = array();
1027
        $wrapper = new Wrapper();
1028
        foreach ($call['params'] as $val) {
1029
            $pt[] = $wrapper->php2XmlrpcType(gettype($val));
1030
        }
1031
1032
        $result = $server->execute($call['methodName'], $call['params'], $pt);
1033
1034
        if ($result->faultCode() != 0) {
1035
            return static::_xmlrpcs_multicall_error($result); // Method returned fault.
1036
        }
1037
1038
        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...
1039
    }
1040
1041
    /**
1042
     * @param Server $server
1043
     * @param Request $req
1044
     * @return Response
1045
     */
1046
    public static function _xmlrpcs_multicall($server, $req)
1047
    {
1048 77
        $result = array();
1049
        // let accept a plain list of php parameters, beside a single xmlrpc msg object
1050 77
        if (is_object($req)) {
1051 77
            $calls = $req->getParam(0);
1052 77
            foreach($calls as $call) {
1053 58
                $result[] = static::_xmlrpcs_multicall_do_call($server, $call);
1054 77
            }
1055 77
        } else {
1056
            $numCalls = count($req);
1057
            for ($i = 0; $i < $numCalls; $i++) {
1058
                $result[$i] = static::_xmlrpcs_multicall_do_call_phpvals($server, $req[$i]);
1059
            }
1060
        }
1061
1062 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...
1063
    }
1064
1065
    /**
1066
     * Error handler used to track errors that occur during server-side execution of PHP code.
1067
     * This allows to report back to the client whether an internal error has occurred or not
1068
     * using an xmlrpc response object, instead of letting the client deal with the html junk
1069
     * that a PHP execution error on the server generally entails.
1070
     *
1071
     * NB: in fact a user defined error handler can only handle WARNING, NOTICE and USER_* errors.
1072
     */
1073
    public static function _xmlrpcs_errorHandler($errCode, $errString, $filename = null, $lineNo = null, $context = null)
1074
    {
1075
        // obey the @ protocol
1076 39
        if (error_reporting() == 0) {
1077 20
            return;
1078
        }
1079
1080
        //if($errCode != E_NOTICE && $errCode != E_WARNING && $errCode != E_USER_NOTICE && $errCode != E_USER_WARNING)
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1081 20
        if ($errCode != E_STRICT) {
1082 20
            \PhpXmlRpc\Server::error_occurred($errString);
1083
        }
1084
        // Try to avoid as much as possible disruption to the previous error handling
1085
        // mechanism in place
1086 20
        if (self::$_xmlrpcs_prev_ehandler == '') {
1087
            // The previous error handler was the default: all we should do is log error
1088
            // to the default error log (if level high enough)
1089 20
            if (ini_get('log_errors') && (intval(ini_get('error_reporting')) & $errCode)) {
1090 20
                error_log($errString);
1091
            }
1092 20
        } else {
1093
            // Pass control on to previous error handler, trying to avoid loops...
1094
            if (self::$_xmlrpcs_prev_ehandler != array('\PhpXmlRpc\Server', '_xmlrpcs_errorHandler')) {
1095
                if (is_array(self::$_xmlrpcs_prev_ehandler)) {
1096
                    // the following works both with static class methods and plain object methods as error handler
1097
                    call_user_func_array(self::$_xmlrpcs_prev_ehandler, array($errCode, $errString, $filename, $lineNo, $context));
1098
                } else {
1099
                    $method = self::$_xmlrpcs_prev_ehandler;
1100
                    $method($errCode, $errString, $filename, $lineNo, $context);
1101
                }
1102
            }
1103
        }
1104 20
    }
1105
}
1106