Completed
Push — master ( 96da4e...7c5656 )
by Michael
04:52
created

IXR_Error::getXml()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 24
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 7
nc 1
nop 0
dl 0
loc 24
rs 8.9713
c 0
b 0
f 0
1
<?php
2
3
/*
4
   IXR - The Inutio XML-RPC Library - (c) Incutio Ltd 2002
5
   Version 1.62WP - Simon Willison, 11th July 2003 (htmlentities -> htmlspecialchars)
6
           ^^^^^^ (We've made some changes)
7
   Site:   http://scripts.incutio.com/xmlrpc/
8
   Manual: http://scripts.incutio.com/xmlrpc/manual.php
9
   Made available under the BSD License: http://www.opensource.org/licenses/bsd-license.php
10
*/
11
12
/**
13
 * Class IXR_Value
14
 */
15
class IXR_Value
16
{
17
    public $data;
18
    public $type;
19
20
    /**
21
     * IXR_Value constructor.
22
     * @param      $data
23
     * @param bool $type
24
     */
25
    public function __construct($data, $type = false)
26
    {
27
        $this->data = $data;
28
        if (!$type) {
29
            $type = $this->calculateType();
30
        }
31
        $this->type = $type;
32
        if ($type === 'struct') {
33
            /* Turn all the values in the array in to new IXR_Value objects */
34
            foreach ($this->data as $key => $value) {
35
                $this->data[$key] = new IXR_Value($value);
36
            }
37
        }
38
        if ($type === 'array') {
39
            for ($i = 0, $j = count($this->data); $i < $j; ++$i) {
40
                $this->data[$i] = new IXR_Value($this->data[$i]);
41
            }
42
        }
43
    }
44
45
    /**
46
     * @return string
47
     */
48
    public function calculateType()
49
    {
50
        if ($this->data === true || $this->data === false) {
51
            return 'boolean';
52
        }
53
        if (is_int($this->data)) {
54
            return 'int';
55
        }
56
        if (is_float($this->data)) {
57
            return 'double';
58
        }
59
        // Deal with IXR object types base64 and date
60
        if (is_object($this->data) && is_a($this->data, 'IXR_Date')) {
61
            return 'date';
62
        }
63
        if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) {
64
            return 'base64';
65
        }
66
        // If it is a normal PHP object convert it in to a struct
67
        if (is_object($this->data)) {
68
            $this->data = get_object_vars($this->data);
69
70
            return 'struct';
71
        }
72
        if (!is_array($this->data)) {
73
            return 'string';
74
        }
75
        /* We have an array - is it an array or a struct ? */
76
        if ($this->isStruct($this->data)) {
77
            return 'struct';
78
        } else {
79
            return 'array';
80
        }
81
    }
82
83
    /**
84
     * @return bool|string
85
     */
86
    public function getXml()
87
    {
88
        /* Return XML for this value */
89
        switch ($this->type) {
90
            case 'boolean':
91
                return '<boolean>' . ($this->data ? '1' : '0') . '</boolean>';
92
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
93
            case 'int':
94
                return '<int>' . $this->data . '</int>';
95
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
96
            case 'double':
97
                return '<double>' . $this->data . '</double>';
98
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
99
            case 'string':
100
                return '<string>' . htmlspecialchars($this->data) . '</string>';
101
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
102
            case 'array':
103
                $return = '<array><data>' . "\n";
104
                foreach ($this->data as $item) {
105
                    $return .= '  <value>' . $item->getXml() . "</value>\n";
106
                }
107
                $return .= '</data></array>';
108
109
                return $return;
110
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
111
            case 'struct':
112
                $return = '<struct>' . "\n";
113
                foreach ($this->data as $name => $value) {
114
                    $return .= "  <member><name>$name</name><value>";
115
                    $return .= $value->getXml() . "</value></member>\n";
116
                }
117
                $return .= '</struct>';
118
119
                return $return;
120
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
121
            case 'date':
122
            case 'base64':
123
                return $this->data->getXml();
0 ignored issues
show
Bug introduced by
The method getXml cannot be called on $this->data (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
124
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
125
        }
126
127
        return false;
128
    }
129
130
    /**
131
     * @param $array
132
     * @return bool
133
     */
134
    public function isStruct($array)
135
    {
136
        /* Nasty function to check if an array is a struct or not */
137
        $expected = 0;
138
        foreach ($array as $key => $value) {
139
            if ((string)$key != (string)$expected) {
140
                return true;
141
            }
142
            ++$expected;
143
        }
144
145
        return false;
146
    }
147
}
148
149
/**
150
 * Class IXR_Message
151
 */
152
class IXR_Message
153
{
154
    public $message;
155
    public $messageType;  // methodCall / methodResponse / fault
156
    public $faultCode;
157
    public $faultString;
158
    public $methodName;
159
    public $params;
160
    // Current variable stacks
161
    public $_arraystructs      = array();   // The stack used to keep track of the current array/struct
162
    public $_arraystructstypes = array(); // Stack keeping track of if things are structs or array
163
    public $_currentStructName = array();  // A stack as well
164
    public $_param;
165
    public $_value;
166
    public $_currentTag;
167
    public $_currentTagContents;
168
    // The XML parser
169
    public $_parser;
170
171
    /**
172
     * IXR_Message constructor.
173
     * @param $message
174
     */
175
    public function __construct($message)
176
    {
177
        $this->message = $message;
178
    }
179
180
    /**
181
     * @return bool
182
     */
183
    public function parse()
184
    {
185
        // first remove the XML declaration
186
        $this->message = preg_replace('/<\?xml(.*)?\?' . '>/', '', $this->message);
187
        if (trim($this->message) == '') {
188
            return false;
189
        }
190
        $this->_parser = xml_parser_create();
191
        // Set XML parser to take the case of tags in to account
192
        xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
193
        // Set XML parser callback functions
194
        xml_set_object($this->_parser, $this);
195
        xml_set_elementHandler($this->_parser, 'tag_open', 'tag_close');
196
        xml_set_character_dataHandler($this->_parser, 'cdata');
197
        if (!xml_parse($this->_parser, $this->message)) {
198
            /* die(sprintf('XML error: %s at line %d',
0 ignored issues
show
Unused Code Comprehensibility introduced by
68% 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...
199
                xml_error_string(xml_get_error_code($this->_parser)),
200
                xml_get_current_line_number($this->_parser))); */
201
202
            return false;
203
        }
204
        xml_parser_free($this->_parser);
205
        // Grab the error messages, if any
206
        if ($this->messageType === 'fault') {
207
            $this->faultCode   = $this->params[0]['faultCode'];
208
            $this->faultString = $this->params[0]['faultString'];
209
        }
210
211
        return true;
212
    }
213
214
    /**
215
     * @param $parser
216
     * @param $tag
217
     * @param $attr
218
     */
219
    public function tag_open($parser, $tag, $attr)
0 ignored issues
show
Unused Code introduced by
The parameter $parser 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...
Unused Code introduced by
The parameter $attr 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...
220
    {
221
        $this->currentTag = $tag;
0 ignored issues
show
Bug introduced by
The property currentTag does not seem to exist. Did you mean _currentTag?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
222
        switch ($tag) {
223
            case 'methodCall':
224
            case 'methodResponse':
225
            case 'fault':
226
                $this->messageType = $tag;
227
                break;
228
            /* Deal with stacks of arrays and structs */
229
            case 'data':    // data is to all intents and puposes more interesting than array
230
                $this->_arraystructstypes[] = 'array';
231
                $this->_arraystructs[]      = array();
232
                break;
233
            case 'struct':
234
                $this->_arraystructstypes[] = 'struct';
235
                $this->_arraystructs[]      = array();
236
                break;
237
        }
238
    }
239
240
    /**
241
     * @param $parser
242
     * @param $cdata
243
     */
244
    public function cdata($parser, $cdata)
0 ignored issues
show
Unused Code introduced by
The parameter $parser 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...
245
    {
246
        $this->_currentTagContents .= $cdata;
247
    }
248
249
    /**
250
     * @param $parser
251
     * @param $tag
252
     */
253
    public function tag_close($parser, $tag)
0 ignored issues
show
Unused Code introduced by
The parameter $parser 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...
254
    {
255
        $valueFlag = false;
256
        switch ($tag) {
257
            case 'int':
258
            case 'i4':
259
                $value                     = (int)trim($this->_currentTagContents);
260
                $this->_currentTagContents = '';
261
                $valueFlag                 = true;
262
                break;
263
            case 'double':
264
                $value                     = (double)trim($this->_currentTagContents);
265
                $this->_currentTagContents = '';
266
                $valueFlag                 = true;
267
                break;
268
            case 'string':
269
                $value                     = (string)trim($this->_currentTagContents);
270
                $this->_currentTagContents = '';
271
                $valueFlag                 = true;
272
                break;
273
            case 'dateTime.iso8601':
274
                $value = new IXR_Date(trim($this->_currentTagContents));
275
                // $value = $iso->getTimestamp();
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% 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...
276
                $this->_currentTagContents = '';
277
                $valueFlag                 = true;
278
                break;
279 View Code Duplication
            case '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...
280
                // "If no type is indicated, the type is string."
281
                if (trim($this->_currentTagContents) != '') {
282
                    $value                     = (string)$this->_currentTagContents;
283
                    $this->_currentTagContents = '';
284
                    $valueFlag                 = true;
285
                }
286
                break;
287 View Code Duplication
            case 'boolean':
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...
288
                $value                     = (boolean)trim($this->_currentTagContents);
289
                $this->_currentTagContents = '';
290
                $valueFlag                 = true;
291
                break;
292
            case 'base64':
293
                $value                     = base64_decode(trim($this->_currentTagContents));
294
                $this->_currentTagContents = '';
295
                $valueFlag                 = true;
296
                break;
297
            /* Deal with stacks of arrays and structs */
298
            case 'data':
299
            case 'struct':
300
                $value = array_pop($this->_arraystructs);
301
                array_pop($this->_arraystructstypes);
302
                $valueFlag = true;
303
                break;
304
            case 'member':
305
                array_pop($this->_currentStructName);
306
                break;
307
            case 'name':
308
                $this->_currentStructName[] = trim($this->_currentTagContents);
309
                $this->_currentTagContents  = '';
310
                break;
311
            case 'methodName':
312
                $this->methodName          = trim($this->_currentTagContents);
313
                $this->_currentTagContents = '';
314
                break;
315
        }
316
        if ($valueFlag) {
317
            /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% 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...
318
            if (!is_array($value) && !is_object($value)) {
319
                $value = trim($value);
320
            }
321
            */
322
            if (count($this->_arraystructs) > 0) {
323
                // Add value to struct or array
324
                if ($this->_arraystructstypes[count($this->_arraystructstypes) - 1] === 'struct') {
325
                    // Add to struct
326
                    $this->_arraystructs[count($this->_arraystructs) - 1][$this->_currentStructName[count($this->_currentStructName) - 1]] = $value;
0 ignored issues
show
Bug introduced by
The variable $value 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...
327
                } else {
328
                    // Add to array
329
                    $this->_arraystructs[count($this->_arraystructs) - 1][] = $value;
330
                }
331
            } else {
332
                // Just add as a paramater
333
                $this->params[] = $value;
334
            }
335
        }
336
    }
337
}
338
339
/**
340
 * Class IXR_Server
341
 */
342
class IXR_Server
343
{
344
    public $data;
345
    public $callbacks = array();
346
    public $message;
347
    public $capabilities;
348
349
    /**
350
     * IXR_Server constructor.
351
     * @param bool $callbacks
352
     * @param bool $data
353
     */
354
    public function __construct($callbacks = false, $data = false)
355
    {
356
        $this->setCapabilities();
357
        if ($callbacks) {
358
            $this->callbacks = $callbacks;
0 ignored issues
show
Documentation Bug introduced by
It seems like $callbacks of type boolean is incompatible with the declared type array of property $callbacks.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
359
        }
360
        $this->setCallbacks();
361
        $this->serve($data);
362
    }
363
364
    /**
365
     * @param bool $data
366
     */
367
    public function serve($data = false)
368
    {
369
        if (!$data) {
370
            $http_raw_post_data = file_get_contents('php://input');
371
            if (!$http_raw_post_data) {
372
                die('XML-RPC server accepts POST requests only.');
373
            }
374
            $data = $http_raw_post_data;
375
        }
376
        $this->message = new IXR_Message($data);
377
        if (!$this->message->parse()) {
378
            $this->error(-32700, 'parse error. not well formed');
0 ignored issues
show
Documentation introduced by
'parse error. not well formed' is of type string, but the function expects a boolean.

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...
379
        }
380
        if ($this->message->messageType !== 'methodCall') {
381
            $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall');
0 ignored issues
show
Documentation introduced by
'server error. invalid x...t must be a methodCall' is of type string, but the function expects a boolean.

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...
382
        }
383
        $result = $this->call($this->message->methodName, $this->message->params);
384
        // Is the result an error?
385
        if (is_a($result, 'IXR_Error')) {
386
            $this->error($result);
387
        }
388
        // Encode the result
389
        $r         = new IXR_Value($result);
390
        $resultxml = $r->getXml();
391
        // Create the XML
392
        $xml = <<<EOD
393
<methodResponse>
394
  <params>
395
    <param>
396
      <value>
397
        $resultxml
398
      </value>
399
    </param>
400
  </params>
401
</methodResponse>
402
403
EOD;
404
        // Send it
405
        $this->output($xml);
406
    }
407
408
    /**
409
     * @param $methodname
410
     * @param $args
411
     * @return IXR_Error|mixed
412
     */
413
    public function call($methodname, $args)
414
    {
415
        if (!$this->hasMethod($methodname)) {
416
            return new IXR_Error(-32601, 'server error. requested method ' . $methodname . ' does not exist.');
417
        }
418
        $method = $this->callbacks[$methodname];
419
        // Perform the callback and send the response
420
        if (count($args) == 1) {
421
            // If only one paramater just send that instead of the whole array
422
            $args = $args[0];
423
        }
424
        // Are we dealing with a function or a method?
425
        if (substr($method, 0, 5) === 'this:') {
426
            // It's a class method - check it exists
427
            $method = substr($method, 5);
428
            if (!method_exists($this, $method)) {
429
                return new IXR_Error(-32601, 'server error. requested class method "' . $method . '" does not exist.');
430
            }
431
            // Call the method
432
            $result = $this->$method($args);
433
        } else {
434
            // It's a function - does it exist?
435
            if (is_array($method)) {
436
                if (!method_exists($method[0], $method[1])) {
437
                    return new IXR_Error(-32601, 'server error. requested object method "' . $method[1] . '" does not exist.');
438
                }
439
            } elseif (!function_exists($method)) {
440
                return new IXR_Error(-32601, 'server error. requested function "' . $method . '" does not exist.');
441
            }
442
            // Call the function
443
            $result = call_user_func($method, $args);
444
        }
445
446
        return $result;
447
    }
448
449
    /**
450
     * @param      $error
451
     * @param bool $message
452
     */
453
    public function error($error, $message = false)
454
    {
455
        // Accepts either an error object or an error code and message
456
        if ($message && !is_object($error)) {
457
            $error = new IXR_Error($error, $message);
458
        }
459
        $this->output($error->getXml());
460
    }
461
462
    /**
463
     * @param $xml
464
     */
465
    public function output($xml)
466
    {
467
        $xml    = '<?xml version="1.0"?>' . "\n" . $xml;
468
        $length = strlen($xml);
469
        header('Connection: close');
470
        header('Content-Length: ' . $length);
471
        header('Content-Type: text/xml');
472
        header('Date: ' . date('r'));
473
        echo $xml;
474
        exit;
475
    }
476
477
    /**
478
     * @param $method
479
     * @return bool
480
     */
481
    public function hasMethod($method)
482
    {
483
        return in_array($method, array_keys($this->callbacks));
484
    }
485
486
    public function setCapabilities()
487
    {
488
        // Initialises capabilities array
489
        $this->capabilities = array(
490
            'xmlrpc'           => array(
491
                'specUrl'     => 'http://www.xmlrpc.com/spec',
492
                'specVersion' => 1
493
            ),
494
            'faults_interop'   => array(
495
                'specUrl'     => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
496
                'specVersion' => 20010516
497
            ),
498
            'system.multicall' => array(
499
                'specUrl'     => 'http://www.xmlrpc.com/discuss/msgReader$1208',
500
                'specVersion' => 1
501
            )
502
        );
503
    }
504
505
    /**
506
     * @param $args
507
     * @return mixed
508
     */
509
    public function getCapabilities($args)
0 ignored issues
show
Unused Code introduced by
The parameter $args 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...
510
    {
511
        return $this->capabilities;
512
    }
513
514
    public function setCallbacks()
515
    {
516
        $this->callbacks['system.getCapabilities'] = 'this:getCapabilities';
517
        $this->callbacks['system.listMethods']     = 'this:listMethods';
518
        $this->callbacks['system.multicall']       = 'this:multiCall';
519
    }
520
521
    /**
522
     * @param $args
523
     * @return array
524
     */
525
    public function listMethods($args)
0 ignored issues
show
Unused Code introduced by
The parameter $args 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...
526
    {
527
        // Returns a list of methods - uses array_reverse to ensure user defined
528
        // methods are listed before server defined methods
529
        return array_reverse(array_keys($this->callbacks));
530
    }
531
532
    /**
533
     * @param $methodcalls
534
     * @return array
535
     */
536
    public function multiCall($methodcalls)
537
    {
538
        // See http://www.xmlrpc.com/discuss/msgReader$1208
539
        $return = array();
540
        foreach ($methodcalls as $call) {
541
            $method = $call['methodName'];
542
            $params = $call['params'];
543
            if ($method === 'system.multicall') {
544
                $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden');
545
            } else {
546
                $result = $this->call($method, $params);
547
            }
548
            if (is_a($result, 'IXR_Error')) {
549
                $return[] = array(
550
                    'faultCode'   => $result->code,
551
                    'faultString' => $result->message
552
                );
553
            } else {
554
                $return[] = array($result);
555
            }
556
        }
557
558
        return $return;
559
    }
560
}
561
562
/**
563
 * Class IXR_Request
564
 */
565
class IXR_Request
566
{
567
    public $method;
568
    public $args;
569
    public $xml;
570
571
    /**
572
     * IXR_Request constructor.
573
     * @param $method
574
     * @param $args
575
     */
576
    public function __construct($method, $args)
577
    {
578
        $this->method = $method;
579
        $this->args   = $args;
580
        $this->xml    = <<<EOD
581
<?xml version="1.0"?>
582
<methodCall>
583
<methodName>{$this->method}</methodName>
584
<params>
585
586
EOD;
587
        foreach ($this->args as $arg) {
588
            $this->xml .= '<param><value>';
589
            $v         = new IXR_Value($arg);
590
            $this->xml .= $v->getXml();
591
            $this->xml .= "</value></param>\n";
592
        }
593
        $this->xml .= '</params></methodCall>';
594
    }
595
596
    /**
597
     * @return int
598
     */
599
    public function getLength()
600
    {
601
        return strlen($this->xml);
602
    }
603
604
    /**
605
     * @return string
606
     */
607
    public function getXml()
608
    {
609
        return $this->xml;
610
    }
611
}
612
613
/**
614
 * Class IXR_Client
615
 */
616
class IXR_Client
617
{
618
    public $server;
619
    public $port;
620
    public $path;
621
    public $useragent;
622
    public $response;
623
    public $timeout;
624
    public $vendor  = '';
625
    public $message = false;
626
    public $debug   = false;
627
    // Storage place for an error message
628
    public $error = false;
629
630
    /**
631
     * IXR_Client constructor.
632
     * @param        $server
633
     * @param bool   $path
634
     * @param int    $port
635
     * @param int    $timeout
636
     * @param string $vendor
637
     */
638
    public function __construct($server, $path = false, $port = 80, $timeout = 30, $vendor = '')
0 ignored issues
show
Unused Code introduced by
The parameter $vendor 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...
639
    {
640
        if (!$path) {
641
            // Assume we have been given a URL instead
642
            $bits         = parse_url($server);
643
            $this->server = $bits['host'];
644
            $this->port   = isset($bits['port']) ? $bits['port'] : 80;
645
            $this->path   = isset($bits['path']) ? $bits['path'] : '/';
646
            // Make absolutely sure we have a path
647
            if (!$this->path) {
648
                $this->path = '/';
649
            }
650
        } else {
651
            $this->server  = $server;
652
            $this->path    = $path;
653
            $this->port    = $port;
654
            $this->timeout = $timeout;
655
        }
656
        $this->useragent = 'The Incutio XML-RPC PHP Library';
657
    }
658
659
    /**
660
     * @return bool
661
     */
662
    public function query()
663
    {
664
        $args    = func_get_args();
665
        $method  = array_shift($args);
666
        $request = new IXR_Request($method, $args);
667
        $length  = $request->getLength();
668
        $xml     = $request->getXml();
669
        $r       = "\r\n";
670
        $request = "POST {$this->path} HTTP/1.0$r";
671
        $request .= "Host: {$this->server}$r";
672
        $request .= "Content-Type: text/xml$r";
673
        $request .= "User-Agent: {$this->useragent}$r";
674
        $request .= "Content-length: {$length}$r$r";
675
        $request .= $xml;
676
        // Now send the request
677
        if ($this->debug) {
678
            echo '<pre>' . htmlspecialchars($request) . "\n</pre>\n\n";
679
        }
680
        $fp = @fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout);
681
        if (!$fp) {
682
            $this->error = new IXR_Error(-32300, 'transport error - could not open socket');
0 ignored issues
show
Documentation Bug introduced by
It seems like new \IXR_Error(-32300, '...could not open socket') of type object<IXR_Error> is incompatible with the declared type boolean of property $error.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
683
684
            return false;
685
        }
686
        fwrite($fp, $request);
687
        $contents       = '';
688
        $gotFirstLine   = false;
689
        $gettingHeaders = true;
690
        while (!feof($fp)) {
691
            $line = fgets($fp, 4096);
692
            if (!$gotFirstLine) {
693
                // Check line for '200'
694
                if (false === strpos($line, '200')) {
695
                    $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200');
0 ignored issues
show
Documentation Bug introduced by
It seems like new \IXR_Error(-32300, '...atus code was not 200') of type object<IXR_Error> is incompatible with the declared type boolean of property $error.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
696
697
                    return false;
698
                }
699
                $gotFirstLine = true;
700
            }
701
            if (trim($line) == '') {
702
                $gettingHeaders = false;
703
            }
704
            if (!$gettingHeaders) {
705
                $contents .= trim($line) . "\n";
706
            }
707
        }
708
        if ($this->debug) {
709
            echo '<pre>' . htmlspecialchars($contents) . "\n</pre>\n\n";
710
        }
711
        // Now parse what we've got back
712
        $this->message = new IXR_Message($contents);
0 ignored issues
show
Documentation Bug introduced by
It seems like new \IXR_Message($contents) of type object<IXR_Message> is incompatible with the declared type boolean of property $message.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
713
        if (!$this->message->parse()) {
714
            // XML error
715
            $this->error = new IXR_Error(-32700, 'parse error. not well formed');
0 ignored issues
show
Documentation Bug introduced by
It seems like new \IXR_Error(-32700, '...rror. not well formed') of type object<IXR_Error> is incompatible with the declared type boolean of property $error.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
716
717
            return false;
718
        }
719
        // Is the message a fault?
720
        if ($this->message->messageType === 'fault') {
721
            $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);
0 ignored issues
show
Documentation Bug introduced by
It seems like new \IXR_Error($this->me...->message->faultString) of type object<IXR_Error> is incompatible with the declared type boolean of property $error.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
722
723
            return false;
724
        }
725
726
        // Message must be OK
727
        return true;
728
    }
729
730
    /**
731
     * @return mixed
732
     */
733
    public function getResponse()
734
    {
735
        // methodResponses can only have one param - return that
736
        return $this->message->params[0];
737
    }
738
739
    /**
740
     * @return bool
741
     */
742
    public function isError()
743
    {
744
        return is_object($this->error);
745
    }
746
747
    /**
748
     * @return mixed
749
     */
750
    public function getErrorCode()
751
    {
752
        return $this->error->code;
753
    }
754
755
    /**
756
     * @return mixed
757
     */
758
    public function getErrorMessage()
759
    {
760
        return $this->error->message;
761
    }
762
}
763
764
/**
765
 * Class IXR_Error
766
 */
767
class IXR_Error
768
{
769
    public $code;
770
    public $message;
771
772
    /**
773
     * IXR_Error constructor.
774
     * @param $code
775
     * @param $message
776
     */
777
    public function __construct($code, $message)
778
    {
779
        $this->code    = $code;
780
        $this->message = $message;
781
    }
782
783
    /**
784
     * @return string
785
     */
786
    public function getXml()
787
    {
788
        $xml = <<<EOD
789
<methodResponse>
790
  <fault>
791
    <value>
792
      <struct>
793
        <member>
794
          <name>faultCode</name>
795
          <value><int>{$this->code}</int></value>
796
        </member>
797
        <member>
798
          <name>faultString</name>
799
          <value><string>{$this->message}</string></value>
800
        </member>
801
      </struct>
802
    </value>
803
  </fault>
804
</methodResponse>
805
806
EOD;
807
808
        return $xml;
809
    }
810
}
811
812
/**
813
 * Class IXR_Date
814
 */
815
class IXR_Date
816
{
817
    public $year;
818
    public $month;
819
    public $day;
820
    public $hour;
821
    public $minute;
822
    public $second;
823
    public $timezone;
824
825
    /**
826
     * IXR_Date constructor.
827
     * @param $time
828
     */
829
    public function __construct($time)
830
    {
831
        // $time can be a PHP timestamp or an ISO one
832
        if (is_numeric($time)) {
833
            $this->parseTimestamp($time);
834
        } else {
835
            $this->parseIso($time);
836
        }
837
    }
838
839
    /**
840
     * @param $timestamp
841
     */
842
    public function parseTimestamp($timestamp)
843
    {
844
        $this->year   = date('Y', $timestamp);
845
        $this->month  = date('Y', $timestamp);
846
        $this->day    = date('Y', $timestamp);
847
        $this->hour   = date('H', $timestamp);
848
        $this->minute = date('i', $timestamp);
849
        $this->second = date('s', $timestamp);
850
    }
851
852
    /**
853
     * @param $iso
854
     */
855
    public function parseIso($iso)
856
    {
857
        $this->year     = substr($iso, 0, 4);
858
        $this->month    = substr($iso, 4, 2);
859
        $this->day      = substr($iso, 6, 2);
860
        $this->hour     = substr($iso, 9, 2);
861
        $this->minute   = substr($iso, 12, 2);
862
        $this->second   = substr($iso, 15, 2);
863
        $this->timezone = substr($iso, 17);
864
    }
865
866
    /**
867
     * @return string
868
     */
869
    public function getIso()
870
    {
871
        return $this->year . $this->month . $this->day . 'T' . $this->hour . ':' . $this->minute . ':' . $this->second . $this->timezone;
872
    }
873
874
    /**
875
     * @return string
876
     */
877
    public function getXml()
878
    {
879
        return '<dateTime.iso8601>' . $this->getIso() . '</dateTime.iso8601>';
880
    }
881
882
    /**
883
     * @return int
884
     */
885
    public function getTimestamp()
886
    {
887
        return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);
888
    }
889
}
890
891
/**
892
 * Class IXR_Base64
893
 */
894
class IXR_Base64
895
{
896
    public $data;
897
898
    /**
899
     * IXR_Base64 constructor.
900
     * @param $data
901
     */
902
    public function __construct($data)
903
    {
904
        $this->data = $data;
905
    }
906
907
    /**
908
     * @return string
909
     */
910
    public function getXml()
911
    {
912
        return '<base64>' . base64_encode($this->data) . '</base64>';
913
    }
914
}
915
916
/**
917
 * Class IXR_IntrospectionServer
918
 */
919
class IXR_IntrospectionServer extends IXR_Server
920
{
921
    public $signatures;
922
    public $help;
923
924
    /**
925
     * IXR_IntrospectionServer constructor.
926
     */
927
    public function __construct()
928
    {
929
        $this->setCallbacks();
930
        $this->setCapabilities();
931
        $this->capabilities['introspection'] = array(
932
            'specUrl'     => 'http://xmlrpc.usefulinc.com/doc/reserved.html',
933
            'specVersion' => 1
934
        );
935
        $this->addCallback('system.methodSignature', 'this:methodSignature', array('array', 'string'), 'Returns an array describing the return type and required parameters of a method');
936
        $this->addCallback('system.getCapabilities', 'this:getCapabilities', array('struct'), 'Returns a struct describing the XML-RPC specifications supported by this server');
937
        $this->addCallback('system.listMethods', 'this:listMethods', array('array'), 'Returns an array of available methods on this server');
938
        $this->addCallback('system.methodHelp', 'this:methodHelp', array('string', 'string'), 'Returns a documentation string for the specified method');
939
    }
940
941
    /**
942
     * @param $method
943
     * @param $callback
944
     * @param $args
945
     * @param $help
946
     */
947
    public function addCallback($method, $callback, $args, $help)
948
    {
949
        $this->callbacks[$method]  = $callback;
950
        $this->signatures[$method] = $args;
951
        $this->help[$method]       = $help;
952
    }
953
954
    /**
955
     * @param $methodname
956
     * @param $args
957
     * @return IXR_Error|mixed
958
     */
959
    public function call($methodname, $args)
960
    {
961
        // Make sure it's in an array
962
        if ($args && !is_array($args)) {
963
            $args = array($args);
964
        }
965
        // Over-rides default call method, adds signature check
966
        if (!$this->hasMethod($methodname)) {
967
            return new IXR_Error(-32601, 'server error. requested method "' . $this->message->methodName . '" not specified.');
968
        }
969
        $method     = $this->callbacks[$methodname];
0 ignored issues
show
Unused Code introduced by
$method 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...
970
        $signature  = $this->signatures[$methodname];
971
        $returnType = array_shift($signature);
0 ignored issues
show
Unused Code introduced by
$returnType 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...
972
        // Check the number of arguments
973
        if (count($args) != count($signature)) {
974
            // print 'Num of args: '.count($args).' Num in signature: '.count($signature);
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% 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...
975
            return new IXR_Error(-32602, 'server error. wrong number of method parameters');
976
        }
977
        // Check the argument types
978
        $ok         = true;
979
        $argsbackup = $args;
980
        for ($i = 0, $j = count($args); $i < $j; ++$i) {
981
            $arg  = array_shift($args);
982
            $type = array_shift($signature);
983
            switch ($type) {
984
                case 'int':
985
                case 'i4':
986
                    if (is_array($arg) || !is_int($arg)) {
987
                        $ok = false;
988
                    }
989
                    break;
990
                case 'base64':
991
                case 'string':
992
                    if (!is_string($arg)) {
993
                        $ok = false;
994
                    }
995
                    break;
996
                case 'boolean':
997
                    if ($arg !== false && $arg !== true) {
998
                        $ok = false;
999
                    }
1000
                    break;
1001
                case 'float':
1002
                case 'double':
1003
                    if (!is_float($arg)) {
1004
                        $ok = false;
1005
                    }
1006
                    break;
1007
                case 'date':
1008
                case 'dateTime.iso8601':
1009
                    if (!is_a($arg, 'IXR_Date')) {
1010
                        $ok = false;
1011
                    }
1012
                    break;
1013
            }
1014
            if (!$ok) {
1015
                return new IXR_Error(-32602, 'server error. invalid method parameters');
1016
            }
1017
        }
1018
1019
        // It passed the test - run the "real" method call
1020
        return parent::call($methodname, $argsbackup);
1021
    }
1022
1023
    /**
1024
     * @param $method
1025
     * @return array|IXR_Error
1026
     */
1027
    public function methodSignature($method)
1028
    {
1029
        if (!$this->hasMethod($method)) {
1030
            return new IXR_Error(-32601, 'server error. requested method "' . $method . '" not specified.');
1031
        }
1032
        // We should be returning an array of types
1033
        $types  = $this->signatures[$method];
1034
        $return = array();
1035
        foreach ($types as $type) {
1036
            switch ($type) {
1037
                case 'string':
1038
                    $return[] = 'string';
1039
                    break;
1040
                case 'int':
1041
                case 'i4':
1042
                    $return[] = 42;
1043
                    break;
1044
                case 'double':
1045
                    $return[] = 3.1415;
1046
                    break;
1047
                case 'dateTime.iso8601':
1048
                    $return[] = new IXR_Date(time());
1049
                    break;
1050
                case 'boolean':
1051
                    $return[] = true;
1052
                    break;
1053
                case 'base64':
1054
                    $return[] = new IXR_Base64('base64');
1055
                    break;
1056
                case 'array':
1057
                    $return[] = array('array');
1058
                    break;
1059
                case 'struct':
1060
                    $return[] = array('struct' => 'struct');
1061
                    break;
1062
            }
1063
        }
1064
1065
        return $return;
1066
    }
1067
1068
    /**
1069
     * @param $method
1070
     * @return mixed
1071
     */
1072
    public function methodHelp($method)
1073
    {
1074
        return $this->help[$method];
1075
    }
1076
}
1077
1078
/**
1079
 * Class IXR_ClientMulticall
1080
 */
1081
class IXR_ClientMulticall extends IXR_Client
1082
{
1083
    public $calls = array();
1084
1085
    /**
1086
     * IXR_ClientMulticall constructor.
1087
     * @param      $server
1088
     * @param bool $path
1089
     * @param int  $port
1090
     */
1091
    public function __construct($server, $path = false, $port = 80)
1092
    {
1093
        parent::IXR_Client($server, $path, $port);
0 ignored issues
show
Bug introduced by
The method IXR_Client() does not seem to exist on object<IXR_Client>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Comprehensibility Bug introduced by
It seems like you call parent on a different method (IXR_Client() instead of __construct()). Are you sure this is correct? If so, you might want to change this to $this->IXR_Client().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
1094
        $this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)';
1095
    }
1096
1097
    public function addCall()
1098
    {
1099
        $args          = func_get_args();
1100
        $methodName    = array_shift($args);
1101
        $struct        = array(
1102
            'methodName' => $methodName,
1103
            'params'     => $args
1104
        );
1105
        $this->calls[] = $struct;
1106
    }
1107
1108
    /**
1109
     * @return bool
1110
     */
1111
    public function query()
1112
    {
1113
        // Prepare multicall, then call the parent::query() method
1114
        return parent::query('system.multicall', $this->calls);
1115
    }
1116
}
1117