Completed
Push — master ( 3167db...70e3fd )
by Gaetano
07:00
created

Server::xml_header()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 8
ccs 3
cts 3
cp 1
crap 2
rs 10
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 470
    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 470
        if (function_exists('gzinflate')) {
104 470
            $this->accepted_compression = array('gzip', 'deflate');
105 470
            $this->compress_response = true;
106
        }
107
108
        // by default the xml parser can support these 3 charset encodings
109 470
        $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 470
        if ($dispatchMap) {
118 469
            $this->dmap = $dispatchMap;
119 469
            if ($serviceNow) {
120
                $this->service();
121
            }
122
        }
123 470
    }
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 469
    public function setDebug($level)
140
    {
141 469
        $this->debug = $level;
142 469
    }
143
144
    /**
145
     * Add a string to the debug info that can be later serialized by the server as part of the response message.
146
     * Note that for best compatibility, the debug string should be encoded using the PhpXmlRpc::$xmlrpc_internalencoding
147
     * character set.
148
     *
149
     * @param string $msg
150
     * @access public
151
     */
152 2
    public static function xmlrpc_debugmsg($msg)
153
    {
154 2
        static::$_xmlrpc_debuginfo .= $msg . "\n";
155 2
    }
156
157
    /**
158
     * @param string $msg
159
     */
160
    public static function error_occurred($msg)
161
    {
162
        static::$_xmlrpcs_occurred_errors .= $msg . "\n";
163
    }
164
165
    /**
166
     * Return a string with the serialized representation of all debug info.
167
     *
168
     * @param string $charsetEncoding the target charset encoding for the serialization
169
     *
170
     * @return string an XML comment (or two)
171
     */
172 469
    public function serializeDebug($charsetEncoding = '')
173
    {
174
        // Tough encoding problem: which internal charset should we assume for debug info?
175
        // It might contain a copy of raw data received from client, ie with unknown encoding,
176
        // intermixed with php generated data and user generated data...
177
        // so we split it: system debug is base 64 encoded,
178
        // user debug info should be encoded by the end user using the INTERNAL_ENCODING
179 469
        $out = '';
180 469
        if ($this->debug_info != '') {
181 469
            $out .= "<!-- SERVER DEBUG INFO (BASE64 ENCODED):\n" . base64_encode($this->debug_info) . "\n-->\n";
182
        }
183 469
        if (static::$_xmlrpc_debuginfo != '') {
184 2
            $out .= "<!-- DEBUG INFO:\n" . Charset::instance()->encodeEntities(str_replace('--', '_-', static::$_xmlrpc_debuginfo), PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "\n-->\n";
185
            // NB: a better solution MIGHT be to use CDATA, but we need to insert it
186
            // into return payload AFTER the beginning tag
187
            //$out .= "<![CDATA[ DEBUG INFO:\n\n" . str_replace(']]>', ']_]_>', static::$_xmlrpc_debuginfo) . "\n]]>\n";
188
        }
189
190 469
        return $out;
191
    }
192
193
    /**
194
     * Execute the xmlrpc request, printing the response.
195
     *
196
     * @param string $data the request body. If null, the http POST request will be examined
197
     * @param bool $returnPayload When true, return the response but do not echo it or any http header
198
     *
199
     * @return Response|string the response object (usually not used by caller...) or its xml serialization
200
     *
201
     * @throws \Exception in case the executed method does throw an exception (and depending on server configuration)
202
     */
203 469
    public function service($data = null, $returnPayload = false)
204
    {
205 469
        if ($data === null) {
206 469
            $data = file_get_contents('php://input');
207
        }
208 469
        $rawData = $data;
209
210
        // reset internal debug info
211 469
        $this->debug_info = '';
212
213
        // Save what we received, before parsing it
214 469
        if ($this->debug > 1) {
215 469
            $this->debugmsg("+++GOT+++\n" . $data . "\n+++END+++");
216
        }
217
218 469
        $r = $this->parseRequestHeaders($data, $reqCharset, $respCharset, $respEncoding);
219 469
        if (!$r) {
220
            // this actually executes the request
221 469
            $r = $this->parseRequest($data, $reqCharset);
222
        }
223
224
        // save full body of request into response, for more debugging usages
225 469
        $r->raw_data = $rawData;
226
227 469
        if ($this->debug > 2 && static::$_xmlrpcs_occurred_errors) {
228
            $this->debugmsg("+++PROCESSING ERRORS AND WARNINGS+++\n" .
229
                static::$_xmlrpcs_occurred_errors . "+++END+++");
230
        }
231
232 469
        $payload = $this->xml_header($respCharset);
233 469
        if ($this->debug > 0) {
234 469
            $payload = $payload . $this->serializeDebug($respCharset);
235
        }
236
237
        // Do not create response serialization if it has already happened. Helps building json magic
238 469
        if (empty($r->payload)) {
239 469
            $r->serialize($respCharset);
240
        }
241 469
        $payload = $payload . $r->payload;
242
243 469
        if ($returnPayload) {
244
            return $payload;
245
        }
246
247
        // if we get a warning/error that has output some text before here, then we cannot
248
        // add a new header. We cannot say we are sending xml, either...
249 469
        if (!headers_sent()) {
250 469
            header('Content-Type: ' . $r->content_type);
251
            // we do not know if client actually told us an accepted charset, but if he did
252
            // we have to tell him what we did
253 469
            header("Vary: Accept-Charset");
254
255
            // http compression of output: only
256
            // if we can do it, and we want to do it, and client asked us to,
257
            // and php ini settings do not force it already
258 469
            $phpNoSelfCompress = !ini_get('zlib.output_compression') && (ini_get('output_handler') != 'ob_gzhandler');
259 469
            if ($this->compress_response && function_exists('gzencode') && $respEncoding != ''
260 96
                && $phpNoSelfCompress
261
            ) {
262 96
                if (strpos($respEncoding, 'gzip') !== false) {
263 48
                    $payload = gzencode($payload);
264 48
                    header("Content-Encoding: gzip");
265 48
                    header("Vary: Accept-Encoding");
266 48
                } elseif (strpos($respEncoding, 'deflate') !== false) {
267 48
                    $payload = gzcompress($payload);
268 48
                    header("Content-Encoding: deflate");
269 48
                    header("Vary: Accept-Encoding");
270
                }
271
            }
272
273
            // do not output content-length header if php is compressing output for us:
274
            // it will mess up measurements
275 469
            if ($phpNoSelfCompress) {
276 469
                header('Content-Length: ' . (int)strlen($payload));
277
            }
278
        } else {
279
            Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': http headers already sent before response is fully generated. Check for php warning or error messages');
280
        }
281
282 469
        print $payload;
283
284
        // return request, in case subclasses want it
285 469
        return $r;
286
    }
287
288
    /**
289
     * Add a method to the dispatch map.
290
     *
291
     * @param string $methodName the name with which the method will be made available
292
     * @param string $function the php function that will get invoked
293
     * @param array $sig the array of valid method signatures
294
     * @param string $doc method documentation
295
     * @param array $sigDoc the array of valid method signatures docs (one string per param, one for return type)
296
     */
297
    public function add_to_map($methodName, $function, $sig = null, $doc = false, $sigDoc = false)
298
    {
299
        $this->dmap[$methodName] = array(
300
            'function' => $function,
301
            'docstring' => $doc,
302
        );
303
        if ($sig) {
304
            $this->dmap[$methodName]['signature'] = $sig;
305
        }
306
        if ($sigDoc) {
307
            $this->dmap[$methodName]['signature_docs'] = $sigDoc;
308
        }
309
    }
310
311
    /**
312
     * Verify type and number of parameters received against a list of known signatures.
313
     *
314
     * @param array|Request $in array of either xmlrpc value objects or xmlrpc type definitions
315
     * @param array $sigs array of known signatures to match against
316
     *
317
     * @return array
318
     */
319 467
    protected function verifySignature($in, $sigs)
320
    {
321
        // check each possible signature in turn
322 467
        if (is_object($in)) {
323 467
            $numParams = $in->getNumParams();
324
        } else {
325
            $numParams = count($in);
326
        }
327 467
        foreach ($sigs as $curSig) {
328 467
            if (count($curSig) == $numParams + 1) {
329 467
                $itsOK = 1;
330 467
                for ($n = 0; $n < $numParams; $n++) {
331 448
                    if (is_object($in)) {
332 448
                        $p = $in->getParam($n);
333 448
                        if ($p->kindOf() == 'scalar') {
334 391
                            $pt = $p->scalartyp();
335
                        } else {
336 134
                            $pt = $p->kindOf();
337
                        }
338
                    } else {
339
                        $pt = ($in[$n] == 'i4') ? 'int' : strtolower($in[$n]); // dispatch maps never use i4...
340
                    }
341
342
                    // param index is $n+1, as first member of sig is return type
343 448
                    if ($pt != $curSig[$n + 1] && $curSig[$n + 1] != Value::$xmlrpcValue) {
344 19
                        $itsOK = 0;
345 19
                        $pno = $n + 1;
346 19
                        $wanted = $curSig[$n + 1];
347 19
                        $got = $pt;
348 19
                        break;
349
                    }
350
                }
351 467
                if ($itsOK) {
352 467
                    return array(1, '');
353
                }
354
            }
355
        }
356 19
        if (isset($wanted)) {
357
            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...
358
        } else {
359 19
            return array(0, "No method signature matches number of parameters");
360
        }
361
    }
362
363
    /**
364
     * Parse http headers received along with xmlrpc request. If needed, inflate request.
365
     *
366
     * @return mixed Response|null on success or an error Response
367
     */
368
    protected function parseRequestHeaders(&$data, &$reqEncoding, &$respEncoding, &$respCompression)
369
    {
370
        // check if $_SERVER is populated: it might have been disabled via ini file
371
        // (this is true even when in CLI mode)
372 469
        if (count($_SERVER) == 0) {
373
            Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': cannot parse request headers as $_SERVER is not populated');
374
        }
375
376 469
        if ($this->debug > 1) {
377 469
            if (function_exists('getallheaders')) {
378 469
                $this->debugmsg(''); // empty line
379 469
                foreach (getallheaders() as $name => $val) {
380 469
                    $this->debugmsg("HEADER: $name: $val");
381
                }
382
            }
383
        }
384
385 469
        if (isset($_SERVER['HTTP_CONTENT_ENCODING'])) {
386 96
            $contentEncoding = str_replace('x-', '', $_SERVER['HTTP_CONTENT_ENCODING']);
387
        } else {
388 373
            $contentEncoding = '';
389
        }
390
391
        // check if request body has been compressed and decompress it
392 469
        if ($contentEncoding != '' && strlen($data)) {
393 96
            if ($contentEncoding == 'deflate' || $contentEncoding == 'gzip') {
394
                // if decoding works, use it. else assume data wasn't gzencoded
395 96
                if (function_exists('gzinflate') && in_array($contentEncoding, $this->accepted_compression)) {
396 96
                    if ($contentEncoding == 'deflate' && $degzdata = @gzuncompress($data)) {
397 48
                        $data = $degzdata;
398 48 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...
399 48
                            $this->debugmsg("\n+++INFLATED REQUEST+++[" . strlen($data) . " chars]+++\n" . $data . "\n+++END+++");
400
                        }
401 48
                    } elseif ($contentEncoding == 'gzip' && $degzdata = @gzinflate(substr($data, 10))) {
402 48
                        $data = $degzdata;
403 48 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...
404 48
                            $this->debugmsg("+++INFLATED REQUEST+++[" . strlen($data) . " chars]+++\n" . $data . "\n+++END+++");
405
                        }
406
                    } else {
407
                        $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_decompress_fail'], PhpXmlRpc::$xmlrpcstr['server_decompress_fail']);
408
409
                        return $r;
410
                    }
411
                } else {
412
                    $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_cannot_decompress'], PhpXmlRpc::$xmlrpcstr['server_cannot_decompress']);
413
414
                    return $r;
415
                }
416
            }
417
        }
418
419
        // check if client specified accepted charsets, and if we know how to fulfill
420
        // the request
421 469
        if ($this->response_charset_encoding == 'auto') {
422
            $respEncoding = '';
423
            if (isset($_SERVER['HTTP_ACCEPT_CHARSET'])) {
424
                // here we should check if we can match the client-requested encoding
425
                // with the encodings we know we can generate.
426
                /// @todo we should parse q=0.x preferences instead of getting first charset specified...
427
                $clientAcceptedCharsets = explode(',', strtoupper($_SERVER['HTTP_ACCEPT_CHARSET']));
428
                // Give preference to internal encoding
429
                $knownCharsets = array(PhpXmlRpc::$xmlrpc_internalencoding, 'UTF-8', 'ISO-8859-1', 'US-ASCII');
430
                foreach ($knownCharsets as $charset) {
431
                    foreach ($clientAcceptedCharsets as $accepted) {
432
                        if (strpos($accepted, $charset) === 0) {
433
                            $respEncoding = $charset;
434
                            break;
435
                        }
436
                    }
437
                    if ($respEncoding) {
438
                        break;
439
                    }
440
                }
441
            }
442
        } else {
443 469
            $respEncoding = $this->response_charset_encoding;
444
        }
445
446 469
        if (isset($_SERVER['HTTP_ACCEPT_ENCODING'])) {
447 96
            $respCompression = $_SERVER['HTTP_ACCEPT_ENCODING'];
448
        } else {
449 373
            $respCompression = '';
450
        }
451
452
        // 'guestimate' request encoding
453
        /// @todo check if mbstring is enabled and automagic input conversion is on: it might mingle with this check???
454 469
        $reqEncoding = XMLParser::guessEncoding(isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : '',
455
            $data);
456
457 469
        return null;
458
    }
459
460
    /**
461
     * Parse an xml chunk containing an xmlrpc request and execute the corresponding
462
     * php function registered with the server.
463
     *
464
     * @param string $data the xml request
465
     * @param string $reqEncoding (optional) the charset encoding of the xml request
466
     *
467
     * @return Response
468
     *
469
     * @throws \Exception in case the executed method does throw an exception (and depending on server configuration)
470
     */
471
    public function parseRequest($data, $reqEncoding = '')
472
    {
473
        // decompose incoming XML into request structure
474
475 470 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...
476
            // Since parsing will fail if charset is not specified in the xml prologue,
477
            // the encoding is not UTF8 and there are non-ascii chars in the text, we try to work round that...
478
            // The following code might be better for mb_string enabled installs, but
479
            // makes the lib about 200% slower...
480
            //if (!is_valid_charset($reqEncoding, array('UTF-8')))
481 469
            if (!in_array($reqEncoding, array('UTF-8', 'US-ASCII')) && !XMLParser::hasEncoding($data)) {
482 4
                if ($reqEncoding == 'ISO-8859-1') {
483 2
                    $data = utf8_encode($data);
484
                } else {
485 2
                    if (extension_loaded('mbstring')) {
486 2
                        $data = mb_convert_encoding($data, 'UTF-8', $reqEncoding);
487
                    } else {
488
                        Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': invalid charset encoding of received request: ' . $reqEncoding);
489
                    }
490
                }
491
            }
492
        }
493
494
        // PHP internally might use ISO-8859-1, so we have to tell the xml parser to give us back data in the expected charset.
495
        // What if internal encoding is not in one of the 3 allowed? We use the broadest one, ie. utf8
496
        // This allows to send data which is native in various charset,
497
        // by extending xmlrpc_encode_entities() and setting xmlrpc_internalencoding
498 470 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...
499
            $options = array(XML_OPTION_TARGET_ENCODING => 'UTF-8');
500
        } else {
501 470
            $options = array(XML_OPTION_TARGET_ENCODING => PhpXmlRpc::$xmlrpc_internalencoding);
502
        }
503
504 470
        $xmlRpcParser = new XMLParser($options);
505 470
        $xmlRpcParser->parse($data, $this->functions_parameters_type, XMLParser::ACCEPT_REQUEST);
506 470
        if ($xmlRpcParser->_xh['isf'] > 2) {
507
            // (BC) we return XML error as a faultCode
508
            preg_match('/^XML error ([0-9]+)/', $xmlRpcParser->_xh['isf_reason'], $matches);
509
            $r = new Response(0,
510
                PhpXmlRpc::$xmlrpcerrxml + $matches[1],
511
                $xmlRpcParser->_xh['isf_reason']);
512 470 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...
513 1
            $r = new Response(0,
514 1
                PhpXmlRpc::$xmlrpcerr['invalid_request'],
515 1
                PhpXmlRpc::$xmlrpcstr['invalid_request'] . ' ' . $xmlRpcParser->_xh['isf_reason']);
516
        } else {
517
            // small layering violation in favor of speed and memory usage:
518
            // we should allow the 'execute' method handle this, but in the
519
            // most common scenario (xmlrpc values type server with some methods
520
            // registered as phpvals) that would mean a useless encode+decode pass
521 469
            if ($this->functions_parameters_type != 'xmlrpcvals' ||
522 469
                (isset($this->dmap[$xmlRpcParser->_xh['method']]['parameters_type']) &&
523
                    ($this->dmap[$xmlRpcParser->_xh['method']]['parameters_type'] == 'phpvals')
524
                )
525
            ) {
526
                if ($this->debug > 1) {
527
                    $this->debugmsg("\n+++PARSED+++\n" . var_export($xmlRpcParser->_xh['params'], true) . "\n+++END+++");
528
                }
529
                $r = $this->execute($xmlRpcParser->_xh['method'], $xmlRpcParser->_xh['params'], $xmlRpcParser->_xh['pt']);
530
            } else {
531
                // build a Request object with data parsed from xml
532 469
                $req = new Request($xmlRpcParser->_xh['method']);
533
                // now add parameters in
534 469 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...
535 450
                    $req->addParam($xmlRpcParser->_xh['params'][$i]);
536
                }
537
538 469
                if ($this->debug > 1) {
539 469
                    $this->debugmsg("\n+++PARSED+++\n" . var_export($req, true) . "\n+++END+++");
540
                }
541 469
                $r = $this->execute($req);
542
            }
543
        }
544
545 470
        return $r;
546
    }
547
548
    /**
549
     * Execute a method invoked by the client, checking parameters used.
550
     *
551
     * @param mixed $req either a Request obj or a method name
552
     * @param array $params array with method parameters as php types (if m is method name only)
553
     * @param array $paramTypes array with xmlrpc types of method parameters (if m is method name only)
554
     *
555
     * @return Response
556
     *
557
     * @throws \Exception in case the executed method does throw an exception (and depending on server configuration)
558
     */
559
    protected function execute($req, $params = null, $paramTypes = null)
560
    {
561 469
        static::$_xmlrpcs_occurred_errors = '';
562 469
        static::$_xmlrpc_debuginfo = '';
563
564 469
        if (is_object($req)) {
565 469
            $methName = $req->method();
566
        } else {
567
            $methName = $req;
568
        }
569 469
        $sysCall = $this->allow_system_funcs && (strpos($methName, "system.") === 0);
570 469
        $dmap = $sysCall ? $this->getSystemDispatchMap() : $this->dmap;
571
572 469 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...
573
            // No such method
574 77
            return new Response(0,
575 77
                PhpXmlRpc::$xmlrpcerr['unknown_method'],
576 77
                PhpXmlRpc::$xmlrpcstr['unknown_method']);
577
        }
578
579
        // Check signature
580 469
        if (isset($dmap[$methName]['signature'])) {
581 467
            $sig = $dmap[$methName]['signature'];
582 467
            if (is_object($req)) {
583 467
                list($ok, $errStr) = $this->verifySignature($req, $sig);
584
            } else {
585
                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...
586
            }
587 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...
588
                // Didn't match.
589 19
                return new Response(
590 19
                    0,
591 19
                    PhpXmlRpc::$xmlrpcerr['incorrect_params'],
592 19
                    PhpXmlRpc::$xmlrpcstr['incorrect_params'] . ": ${errStr}"
593
                );
594
            }
595
        }
596
597 469
        $func = $dmap[$methName]['function'];
598
        // let the 'class::function' syntax be accepted in dispatch maps
599 469
        if (is_string($func) && strpos($func, '::')) {
600 115
            $func = explode('::', $func);
601
        }
602
603 469
        if (is_array($func)) {
604 117 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...
605 2
                $funcName = get_class($func[0]) . '->' . $func[1];
606
            } else {
607 115
                $funcName = implode('::', $func);
608
            }
609 372
        } else if ($func instanceof \Closure) {
610 97
            $funcName = 'Closure';
611
        } else {
612 294
            $funcName = $func;
613
        }
614
615
        // verify that function to be invoked is in fact callable
616 469 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...
617
            Logger::instance()->errorLog("XML-RPC: " . __METHOD__ . ": function '$funcName' registered as method handler is not callable");
618
            return new Response(
619
                0,
620
                PhpXmlRpc::$xmlrpcerr['server_error'],
621
                PhpXmlRpc::$xmlrpcstr['server_error'] . ": no function matches method"
622
            );
623
        }
624
625
        // If debug level is 3, we should catch all errors generated during
626
        // processing of user function, and log them as part of response
627 469
        if ($this->debug > 2) {
628 469
            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...
629
        }
630
631
        try {
632
            // Allow mixed-convention servers
633 469
            if (is_object($req)) {
634 469
                if ($sysCall) {
635 115
                    $r = call_user_func($func, $this, $req);
636
                } else {
637 374
                    $r = call_user_func($func, $req);
638
                }
639 467
                if (!is_a($r, 'PhpXmlRpc\Response')) {
640
                    Logger::instance()->errorLog("XML-RPC: " . __METHOD__ . ": function '$funcName' registered as method handler does not return an xmlrpc response object but a " . gettype($r));
641 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...
642
                        $r = new Response($r);
643
                    } else {
644
                        $r = new Response(
645
                            0,
646
                            PhpXmlRpc::$xmlrpcerr['server_error'],
647
                            PhpXmlRpc::$xmlrpcstr['server_error'] . ": function does not return xmlrpc response object"
648
                        );
649
                    }
650
                }
651
            } else {
652
                // call a 'plain php' function
653
                if ($sysCall) {
654
                    array_unshift($params, $this);
655
                    $r = call_user_func_array($func, $params);
656
                } else {
657
                    // 3rd API convention for method-handling functions: EPI-style
658
                    if ($this->functions_parameters_type == 'epivals') {
659
                        $r = call_user_func_array($func, array($methName, $params, $this->user_data));
660
                        // mimic EPI behaviour: if we get an array that looks like an error, make it
661
                        // an eror response
662
                        if (is_array($r) && array_key_exists('faultCode', $r) && array_key_exists('faultString', $r)) {
663
                            $r = new Response(0, (integer)$r['faultCode'], (string)$r['faultString']);
664
                        } else {
665
                            // functions using EPI api should NOT return resp objects,
666
                            // so make sure we encode the return type correctly
667
                            $encoder = new Encoder();
668
                            $r = new Response($encoder->encode($r, array('extension_api')));
669
                        }
670
                    } else {
671
                        $r = call_user_func_array($func, $params);
672
                    }
673
                }
674
                // the return type can be either a Response object or a plain php value...
675
                if (!is_a($r, '\PhpXmlRpc\Response')) {
676
                    // what should we assume here about automatic encoding of datetimes
677
                    // and php classes instances???
678
                    $encoder = new Encoder();
679
                    $r = new Response($encoder->encode($r, $this->phpvals_encoding_options));
680
                }
681
            }
682 40
        } catch (\Exception $e) {
683
            // (barring errors in the lib) an uncatched exception happened
684
            // in the called function, we wrap it in a proper error-response
685 40
            switch ($this->exception_handling) {
686 40
                case 2:
687
                    if ($this->debug > 2) {
688
                        if (self::$_xmlrpcs_prev_ehandler) {
689
                            set_error_handler(self::$_xmlrpcs_prev_ehandler);
690
                        } else {
691
                            restore_error_handler();
692
                        }
693
                    }
694
                    throw $e;
695
                    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...
696 40
                case 1:
697 2
                    $r = new Response(0, $e->getCode(), $e->getMessage());
698 2
                    break;
699
                default:
700 40
                    $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_error'], PhpXmlRpc::$xmlrpcstr['server_error']);
701
            }
702
        }
703 469
        if ($this->debug > 2) {
704
            // note: restore the error handler we found before calling the
705
            // user func, even if it has been changed inside the func itself
706 469
            if (self::$_xmlrpcs_prev_ehandler) {
707 58
                set_error_handler(self::$_xmlrpcs_prev_ehandler);
708
            } else {
709 412
                restore_error_handler();
710
            }
711
        }
712
713 469
        return $r;
714
    }
715
716
    /**
717
     * Add a string to the 'internal debug message' (separate from 'user debug message').
718
     *
719
     * @param string $string
720
     */
721
    protected function debugmsg($string)
722
    {
723 469
        $this->debug_info .= $string . "\n";
724 469
    }
725
726
    /**
727
     * @param string $charsetEncoding
728
     * @return string
729
     */
730
    protected function xml_header($charsetEncoding = '')
731
    {
732 469
        if ($charsetEncoding != '') {
733 48
            return "<?xml version=\"1.0\" encoding=\"$charsetEncoding\"?" . ">\n";
734
        } else {
735 421
            return "<?xml version=\"1.0\"?" . ">\n";
736
        }
737
    }
738
739
    /* Functions that implement system.XXX methods of xmlrpc servers */
740
741
    /**
742
     * @return array
743
     */
744
    public function getSystemDispatchMap()
745
    {
746
        return array(
747
            'system.listMethods' => array(
748 115
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_listMethods',
749
                // listMethods: signature was either a string, or nothing.
750
                // The useless string variant has been removed
751 115
                'signature' => array(array(Value::$xmlrpcArray)),
752 115
                'docstring' => 'This method lists all the methods that the XML-RPC server knows how to dispatch',
753
                'signature_docs' => array(array('list of method names')),
754
            ),
755
            'system.methodHelp' => array(
756 115
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_methodHelp',
757 115
                'signature' => array(array(Value::$xmlrpcString, Value::$xmlrpcString)),
758 115
                'docstring' => 'Returns help text if defined for the method passed, otherwise returns an empty string',
759
                'signature_docs' => array(array('method description', 'name of the method to be described')),
760
            ),
761
            'system.methodSignature' => array(
762 115
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_methodSignature',
763 115
                'signature' => array(array(Value::$xmlrpcArray, Value::$xmlrpcString)),
764 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)',
765
                'signature_docs' => array(array('list of known signatures, each sig being an array of xmlrpc type names', 'name of method to be described')),
766
            ),
767
            'system.multicall' => array(
768 115
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_multicall',
769 115
                'signature' => array(array(Value::$xmlrpcArray, Value::$xmlrpcArray)),
770 115
                'docstring' => 'Boxcar multiple RPC calls in one request. See http://www.xmlrpc.com/discuss/msgReader$1208 for details',
771
                '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"')),
772
            ),
773
            'system.getCapabilities' => array(
774 115
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_getCapabilities',
775 115
                'signature' => array(array(Value::$xmlrpcStruct)),
776 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',
777
                'signature_docs' => array(array('list of capabilities, described as structs with a version number and url for the spec')),
778
            ),
779
        );
780
    }
781
782
    /**
783
     * @return array
784
     */
785
    public function getCapabilities()
786
    {
787
        $outAr = array(
788
            // xmlrpc spec: always supported
789
            'xmlrpc' => array(
790
                'specUrl' => 'http://www.xmlrpc.com/spec',
791
                'specVersion' => 1
792
            ),
793
            // if we support system.xxx functions, we always support multicall, too...
794
            // Note that, as of 2006/09/17, the following URL does not respond anymore
795
            'system.multicall' => array(
796
                'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
797
                'specVersion' => 1
798
            ),
799
            // introspection: version 2! we support 'mixed', too
800
            'introspection' => array(
801
                'specUrl' => 'http://phpxmlrpc.sourceforge.net/doc-2/ch10.html',
802
                'specVersion' => 2,
803
            ),
804
        );
805
806
        // NIL extension
807
        if (PhpXmlRpc::$xmlrpc_null_extension) {
808
            $outAr['nil'] = array(
809
                'specUrl' => 'http://www.ontosys.com/xml-rpc/extensions.php',
810
                'specVersion' => 1
811
            );
812
        }
813
814
        return $outAr;
815
    }
816
817
    /**
818
     * @param Server $server
819
     * @param Request $req
820
     * @return Response
821
     */
822
    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...
823
    {
824
        $encoder = new Encoder();
825
        return new Response($encoder->encode($server->getCapabilities()));
826
    }
827
828
    /**
829
     * @param Server $server
830
     * @param Request $req if called in plain php values mode, second param is missing
831
     * @return Response
832
     */
833
    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...
834
    {
835 19
        $outAr = array();
836 19
        foreach ($server->dmap as $key => $val) {
837 19
            $outAr[] = new Value($key, 'string');
838
        }
839 19
        if ($server->allow_system_funcs) {
840 19
            foreach ($server->getSystemDispatchMap() as $key => $val) {
841 19
                $outAr[] = new Value($key, 'string');
842
            }
843
        }
844
845 19
        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...
846
    }
847
848
    /**
849
     * @param Server $server
850
     * @param Request $req
851
     * @return Response
852
     */
853
    public static function _xmlrpcs_methodSignature($server, $req)
854
    {
855
        // let accept as parameter both an xmlrpc value or string
856 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...
857 96
            $methName = $req->getParam(0);
858 96
            $methName = $methName->scalarval();
859
        } else {
860
            $methName = $req;
861
        }
862 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...
863 77
            $dmap = $server->getSystemDispatchMap();
864
        } else {
865 19
            $dmap = $server->dmap;
866
        }
867 96
        if (isset($dmap[$methName])) {
868 96
            if (isset($dmap[$methName]['signature'])) {
869 96
                $sigs = array();
870 96
                foreach ($dmap[$methName]['signature'] as $inSig) {
871 96
                    $curSig = array();
872 96
                    foreach ($inSig as $sig) {
873 96
                        $curSig[] = new Value($sig, 'string');
874
                    }
875 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...
876
                }
877 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...
878
            } else {
879
                // NB: according to the official docs, we should be returning a
880
                // "none-array" here, which means not-an-array
881
                $r = new Response(new Value('undef', 'string'));
882
            }
883
        } else {
884
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['introspect_unknown'], PhpXmlRpc::$xmlrpcstr['introspect_unknown']);
885
        }
886
887 96
        return $r;
888
    }
889
890
    /**
891
     * @param Server $server
892
     * @param Request $req
893
     * @return Response
894
     */
895
    public static function _xmlrpcs_methodHelp($server, $req)
896
    {
897
        // let accept as parameter both an xmlrpc value or string
898 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...
899 77
            $methName = $req->getParam(0);
900 77
            $methName = $methName->scalarval();
901
        } else {
902
            $methName = $req;
903
        }
904 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...
905 77
            $dmap = $server->getSystemDispatchMap();
906
        } else {
907
            $dmap = $server->dmap;
908
        }
909 77
        if (isset($dmap[$methName])) {
910 77
            if (isset($dmap[$methName]['docstring'])) {
911 77
                $r = new Response(new Value($dmap[$methName]['docstring']), 'string');
912
            } else {
913
                $r = new Response(new Value('', 'string'));
914
            }
915
        } else {
916
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['introspect_unknown'], PhpXmlRpc::$xmlrpcstr['introspect_unknown']);
917
        }
918
919 77
        return $r;
920
    }
921
922
    public static function _xmlrpcs_multicall_error($err)
923
    {
924 58
        if (is_string($err)) {
925 58
            $str = PhpXmlRpc::$xmlrpcstr["multicall_${err}"];
926 58
            $code = PhpXmlRpc::$xmlrpcerr["multicall_${err}"];
927
        } else {
928 58
            $code = $err->faultCode();
929 58
            $str = $err->faultString();
930
        }
931 58
        $struct = array();
932 58
        $struct['faultCode'] = new Value($code, 'int');
933 58
        $struct['faultString'] = new Value($str, 'string');
934
935 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...
936
    }
937
938
    /**
939
     * @param Server $server
940
     * @param Value $call
941
     * @return Value
942
     */
943
    public static function _xmlrpcs_multicall_do_call($server, $call)
944
    {
945 58
        if ($call->kindOf() != 'struct') {
946
            return static::_xmlrpcs_multicall_error('notstruct');
947
        }
948 58
        $methName = @$call['methodName'];
949 58
        if (!$methName) {
950
            return static::_xmlrpcs_multicall_error('nomethod');
951
        }
952 58
        if ($methName->kindOf() != 'scalar' || $methName->scalartyp() != 'string') {
953
            return static::_xmlrpcs_multicall_error('notstring');
954
        }
955 58
        if ($methName->scalarval() == 'system.multicall') {
956 58
            return static::_xmlrpcs_multicall_error('recursion');
957
        }
958
959 58
        $params = @$call['params'];
960 58
        if (!$params) {
961
            return static::_xmlrpcs_multicall_error('noparams');
962
        }
963 58
        if ($params->kindOf() != 'array') {
964
            return static::_xmlrpcs_multicall_error('notarray');
965
        }
966
967 58
        $req = new Request($methName->scalarval());
968 58
        foreach($params as $i => $param) {
969 58
            if (!$req->addParam($param)) {
970
                $i++; // for error message, we count params from 1
971
                return static::_xmlrpcs_multicall_error(new Response(0,
972
                    PhpXmlRpc::$xmlrpcerr['incorrect_params'],
973
                    PhpXmlRpc::$xmlrpcstr['incorrect_params'] . ": probable xml error in param " . $i));
974
            }
975
        }
976
977 58
        $result = $server->execute($req);
978
979 58
        if ($result->faultCode() != 0) {
980 58
            return static::_xmlrpcs_multicall_error($result); // Method returned fault.
981
        }
982
983 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...
984
    }
985
986
    /**
987
     * @param Server $server
988
     * @param Value $call
989
     * @return Value
990
     */
991
    public static function _xmlrpcs_multicall_do_call_phpvals($server, $call)
992
    {
993
        if (!is_array($call)) {
994
            return static::_xmlrpcs_multicall_error('notstruct');
995
        }
996
        if (!array_key_exists('methodName', $call)) {
997
            return static::_xmlrpcs_multicall_error('nomethod');
998
        }
999
        if (!is_string($call['methodName'])) {
1000
            return static::_xmlrpcs_multicall_error('notstring');
1001
        }
1002
        if ($call['methodName'] == 'system.multicall') {
1003
            return static::_xmlrpcs_multicall_error('recursion');
1004
        }
1005
        if (!array_key_exists('params', $call)) {
1006
            return static::_xmlrpcs_multicall_error('noparams');
1007
        }
1008
        if (!is_array($call['params'])) {
1009
            return static::_xmlrpcs_multicall_error('notarray');
1010
        }
1011
1012
        // this is a real dirty and simplistic hack, since we might have received a
1013
        // base64 or datetime values, but they will be listed as strings here...
1014
        $pt = array();
1015
        $wrapper = new Wrapper();
1016
        foreach ($call['params'] as $val) {
1017
            $pt[] = $wrapper->php2XmlrpcType(gettype($val));
1018
        }
1019
1020
        $result = $server->execute($call['methodName'], $call['params'], $pt);
1021
1022
        if ($result->faultCode() != 0) {
1023
            return static::_xmlrpcs_multicall_error($result); // Method returned fault.
1024
        }
1025
1026
        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...
1027
    }
1028
1029
    /**
1030
     * @param Server $server
1031
     * @param Request|array $req
1032
     * @return Response
1033
     */
1034
    public static function _xmlrpcs_multicall($server, $req)
1035
    {
1036 77
        $result = array();
1037
        // let accept a plain list of php parameters, beside a single xmlrpc msg object
1038 77
        if (is_object($req)) {
1039 77
            $calls = $req->getParam(0);
1040 77
            foreach($calls as $call) {
1041 58
                $result[] = static::_xmlrpcs_multicall_do_call($server, $call);
1042
            }
1043
        } else {
1044
            $numCalls = count($req);
1045
            for ($i = 0; $i < $numCalls; $i++) {
1046
                $result[$i] = static::_xmlrpcs_multicall_do_call_phpvals($server, $req[$i]);
1047
            }
1048
        }
1049
1050 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...
1051
    }
1052
1053
    /**
1054
     * Error handler used to track errors that occur during server-side execution of PHP code.
1055
     * This allows to report back to the client whether an internal error has occurred or not
1056
     * using an xmlrpc response object, instead of letting the client deal with the html junk
1057
     * that a PHP execution error on the server generally entails.
1058
     *
1059
     * NB: in fact a user defined error handler can only handle WARNING, NOTICE and USER_* errors.
1060
     */
1061
    public static function _xmlrpcs_errorHandler($errCode, $errString, $filename = null, $lineNo = null, $context = null)
1062
    {
1063
        // obey the @ protocol
1064 19
        if (error_reporting() == 0) {
1065 19
            return;
1066
        }
1067
1068
        //if($errCode != E_NOTICE && $errCode != E_WARNING && $errCode != E_USER_NOTICE && $errCode != E_USER_WARNING)
1069
        if ($errCode != E_STRICT) {
1070
            \PhpXmlRpc\Server::error_occurred($errString);
1071
        }
1072
        // Try to avoid as much as possible disruption to the previous error handling
1073
        // mechanism in place
1074
        if (self::$_xmlrpcs_prev_ehandler == '') {
1075
            // The previous error handler was the default: all we should do is log error
1076
            // to the default error log (if level high enough)
1077
            if (ini_get('log_errors') && (intval(ini_get('error_reporting')) & $errCode)) {
1078
                Logger::instance()->errorLog($errString);
1079
            }
1080
        } else {
1081
            // Pass control on to previous error handler, trying to avoid loops...
1082
            if (self::$_xmlrpcs_prev_ehandler != array('\PhpXmlRpc\Server', '_xmlrpcs_errorHandler')) {
1083
                if (is_array(self::$_xmlrpcs_prev_ehandler)) {
1084
                    // the following works both with static class methods and plain object methods as error handler
1085
                    call_user_func_array(self::$_xmlrpcs_prev_ehandler, array($errCode, $errString, $filename, $lineNo, $context));
1086
                } else {
1087
                    $method = self::$_xmlrpcs_prev_ehandler;
1088
                    $method($errCode, $errString, $filename, $lineNo, $context);
1089
                }
1090
            }
1091
        }
1092
    }
1093
}
1094