Completed
Push — master ( 15a86c...ff0242 )
by Michael
03:23
created

IXR_Request::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 13
c 1
b 0
f 0
nc 2
nop 2
dl 0
loc 18
rs 9.4285
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
        $this->data = $data;
27
        if (!$type) {
28
            $type = $this->calculateType();
29
        }
30
        $this->type = $type;
31
        if ($type === 'struct') {
32
            /* Turn all the values in the array in to new IXR_Value objects */
33
            foreach ($this->data as $key => $value) {
34
                $this->data[$key] = new IXR_Value($value);
35
            }
36
        }
37
        if ($type === 'array') {
38
            for ($i = 0, $j = count($this->data); $i < $j; ++$i) {
39
                $this->data[$i] = new IXR_Value($this->data[$i]);
40
            }
41
        }
42
    }
43
44
    /**
45
     * @return string
46
     */
47
    public function calculateType() {
48
        if ($this->data === true || $this->data === false) {
49
            return 'boolean';
50
        }
51
        if (is_int($this->data)) {
52
            return 'int';
53
        }
54
        if (is_float($this->data)) {
55
            return 'double';
56
        }
57
        // Deal with IXR object types base64 and date
58
        if (is_object($this->data) && is_a($this->data, 'IXR_Date')) {
59
            return 'date';
60
        }
61
        if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) {
62
            return 'base64';
63
        }
64
        // If it is a normal PHP object convert it in to a struct
65
        if (is_object($this->data)) {
66
            $this->data = get_object_vars($this->data);
67
68
            return 'struct';
69
        }
70
        if (!is_array($this->data)) {
71
            return 'string';
72
        }
73
        /* We have an array - is it an array or a struct ? */
74
        if ($this->isStruct($this->data)) {
75
            return 'struct';
76
        } else {
77
            return 'array';
78
        }
79
    }
80
81
    /**
82
     * @return bool|string
83
     */
84
    public function getXml() {
85
        /* Return XML for this value */
86
        switch ($this->type) {
87
            case 'boolean':
88
                return '<boolean>' . ($this->data ? '1' : '0') . '</boolean>';
89
                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...
90
            case 'int':
91
                return '<int>' . $this->data . '</int>';
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 'double':
94
                return '<double>' . $this->data . '</double>';
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 'string':
97
                return '<string>' . htmlspecialchars($this->data) . '</string>';
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 'array':
100
                $return = '<array><data>' . "\n";
101
                foreach ($this->data as $item) {
102
                    $return .= '  <value>' . $item->getXml() . "</value>\n";
103
                }
104
                $return .= '</data></array>';
105
106
                return $return;
107
                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...
108
            case 'struct':
109
                $return = '<struct>' . "\n";
110
                foreach ($this->data as $name => $value) {
111
                    $return .= "  <member><name>$name</name><value>";
112
                    $return .= $value->getXml() . "</value></member>\n";
113
                }
114
                $return .= '</struct>';
115
116
                return $return;
117
                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...
118
            case 'date':
119
            case 'base64':
120
                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...
121
                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...
122
        }
123
124
        return false;
125
    }
126
127
    /**
128
     * @param $array
129
     * @return bool
130
     */
131
    public function isStruct($array) {
132
        /* Nasty function to check if an array is a struct or not */
133
        $expected = 0;
134
        foreach ($array as $key => $value) {
135
            if ((string)$key != (string)$expected) {
136
                return true;
137
            }
138
            ++$expected;
139
        }
140
141
        return false;
142
    }
143
}
144
145
/**
146
 * Class IXR_Message
147
 */
148
class IXR_Message
149
{
150
    public $message;
151
    public $messageType;  // methodCall / methodResponse / fault
152
    public $faultCode;
153
    public $faultString;
154
    public $methodName;
155
    public $params;
156
    // Current variable stacks
157
    public $_arraystructs      = array();   // The stack used to keep track of the current array/struct
158
    public $_arraystructstypes = array(); // Stack keeping track of if things are structs or array
159
    public $_currentStructName = array();  // A stack as well
160
    public $_param;
161
    public $_value;
162
    public $_currentTag;
163
    public $_currentTagContents;
164
    // The XML parser
165
    public $_parser;
166
167
    /**
168
     * IXR_Message constructor.
169
     * @param $message
170
     */
171
    public function __construct($message) {
172
        $this->message = $message;
173
    }
174
175
    /**
176
     * @return bool
177
     */
178
    public function parse() {
179
        // first remove the XML declaration
180
        $this->message = preg_replace('/<\?xml(.*)?\?' . '>/', '', $this->message);
181
        if (trim($this->message) == '') {
182
            return false;
183
        }
184
        $this->_parser = xml_parser_create();
185
        // Set XML parser to take the case of tags in to account
186
        xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
187
        // Set XML parser callback functions
188
        xml_set_object($this->_parser, $this);
189
        xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');
190
        xml_set_character_data_handler($this->_parser, 'cdata');
191
        if (!xml_parse($this->_parser, $this->message)) {
192
            /* 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...
193
                xml_error_string(xml_get_error_code($this->_parser)),
194
                xml_get_current_line_number($this->_parser))); */
195
196
            return false;
197
        }
198
        xml_parser_free($this->_parser);
199
        // Grab the error messages, if any
200
        if ($this->messageType === 'fault') {
201
            $this->faultCode   = $this->params[0]['faultCode'];
202
            $this->faultString = $this->params[0]['faultString'];
203
        }
204
205
        return true;
206
    }
207
208
    /**
209
     * @param $parser
210
     * @param $tag
211
     * @param $attr
212
     */
213
    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...
214
        $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...
215
        switch ($tag) {
216
            case 'methodCall':
217
            case 'methodResponse':
218
            case 'fault':
219
                $this->messageType = $tag;
220
                break;
221
            /* Deal with stacks of arrays and structs */
222
            case 'data':    // data is to all intents and puposes more interesting than array
223
                $this->_arraystructstypes[] = 'array';
224
                $this->_arraystructs[]      = array();
225
                break;
226
            case 'struct':
227
                $this->_arraystructstypes[] = 'struct';
228
                $this->_arraystructs[]      = array();
229
                break;
230
        }
231
    }
232
233
    /**
234
     * @param $parser
235
     * @param $cdata
236
     */
237
    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...
238
        $this->_currentTagContents .= $cdata;
239
    }
240
241
    /**
242
     * @param $parser
243
     * @param $tag
244
     */
245
    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...
246
        $valueFlag = false;
247
        switch ($tag) {
248
            case 'int':
249
            case 'i4':
250
                $value                     = (int)trim($this->_currentTagContents);
251
                $this->_currentTagContents = '';
252
                $valueFlag                 = true;
253
                break;
254
            case 'double':
255
                $value                     = (double)trim($this->_currentTagContents);
256
                $this->_currentTagContents = '';
257
                $valueFlag                 = true;
258
                break;
259
            case 'string':
260
                $value                     = (string)trim($this->_currentTagContents);
261
                $this->_currentTagContents = '';
262
                $valueFlag                 = true;
263
                break;
264
            case 'dateTime.iso8601':
265
                $value = new IXR_Date(trim($this->_currentTagContents));
266
                // $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...
267
                $this->_currentTagContents = '';
268
                $valueFlag                 = true;
269
                break;
270 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...
271
                // "If no type is indicated, the type is string."
272
                if (trim($this->_currentTagContents) != '') {
273
                    $value                     = (string)$this->_currentTagContents;
274
                    $this->_currentTagContents = '';
275
                    $valueFlag                 = true;
276
                }
277
                break;
278 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...
279
                $value                     = (boolean)trim($this->_currentTagContents);
280
                $this->_currentTagContents = '';
281
                $valueFlag                 = true;
282
                break;
283
            case 'base64':
284
                $value                     = base64_decode(trim($this->_currentTagContents));
285
                $this->_currentTagContents = '';
286
                $valueFlag                 = true;
287
                break;
288
            /* Deal with stacks of arrays and structs */
289
            case 'data':
290
            case 'struct':
291
                $value = array_pop($this->_arraystructs);
292
                array_pop($this->_arraystructstypes);
293
                $valueFlag = true;
294
                break;
295
            case 'member':
296
                array_pop($this->_currentStructName);
297
                break;
298
            case 'name':
299
                $this->_currentStructName[] = trim($this->_currentTagContents);
300
                $this->_currentTagContents  = '';
301
                break;
302
            case 'methodName':
303
                $this->methodName          = trim($this->_currentTagContents);
304
                $this->_currentTagContents = '';
305
                break;
306
        }
307
        if ($valueFlag) {
308
            /*
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...
309
            if (!is_array($value) && !is_object($value)) {
310
                $value = trim($value);
311
            }
312
            */
313
            if (count($this->_arraystructs) > 0) {
314
                // Add value to struct or array
315
                if ($this->_arraystructstypes[count($this->_arraystructstypes) - 1] === 'struct') {
316
                    // Add to struct
317
                    $this->_arraystructs[count($this->_arraystructs)
318
                                         - 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...
319
                } else {
320
                    // Add to array
321
                    $this->_arraystructs[count($this->_arraystructs) - 1][] = $value;
322
                }
323
            } else {
324
                // Just add as a paramater
325
                $this->params[] = $value;
326
            }
327
        }
328
    }
329
}
330
331
/**
332
 * Class IXR_Server
333
 */
334
class IXR_Server
335
{
336
    public $data;
337
    public $callbacks = array();
338
    public $message;
339
    public $capabilities;
340
341
    /**
342
     * IXR_Server constructor.
343
     * @param bool $callbacks
344
     * @param bool $data
345
     */
346
    public function __construct($callbacks = false, $data = false) {
347
        $this->setCapabilities();
348
        if ($callbacks) {
349
            $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...
350
        }
351
        $this->setCallbacks();
352
        $this->serve($data);
353
    }
354
355
    /**
356
     * @param bool $data
357
     */
358
    public function serve($data = false) {
359
        if (!$data) {
360
            $http_raw_post_data = file_get_contents('php://input');
361
            if (!$http_raw_post_data) {
362
                die('XML-RPC server accepts POST requests only.');
363
            }
364
            $data = $http_raw_post_data;
365
        }
366
        $this->message = new IXR_Message($data);
367
        if (!$this->message->parse()) {
368
            $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...
369
        }
370
        if ($this->message->messageType !== 'methodCall') {
371
            $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...
372
        }
373
        $result = $this->call($this->message->methodName, $this->message->params);
374
        // Is the result an error?
375
        if (is_a($result, 'IXR_Error')) {
376
            $this->error($result);
377
        }
378
        // Encode the result
379
        $r         = new IXR_Value($result);
380
        $resultxml = $r->getXml();
381
        // Create the XML
382
        $xml = <<<EOD
383
<methodResponse>
384
  <params>
385
    <param>
386
      <value>
387
        $resultxml
388
      </value>
389
    </param>
390
  </params>
391
</methodResponse>
392
393
EOD;
394
        // Send it
395
        $this->output($xml);
396
    }
397
398
    /**
399
     * @param $methodname
400
     * @param $args
401
     * @return IXR_Error|mixed
402
     */
403
    public function call($methodname, $args) {
404
        if (!$this->hasMethod($methodname)) {
405
            return new IXR_Error(-32601, 'server error. requested method ' . $methodname . ' does not exist.');
406
        }
407
        $method = $this->callbacks[$methodname];
408
        // Perform the callback and send the response
409
        if (count($args) == 1) {
410
            // If only one paramater just send that instead of the whole array
411
            $args = $args[0];
412
        }
413
        // Are we dealing with a function or a method?
414
        if (substr($method, 0, 5) === 'this:') {
415
            // It's a class method - check it exists
416
            $method = substr($method, 5);
417
            if (!method_exists($this, $method)) {
418
                return new IXR_Error(-32601, 'server error. requested class method "' . $method . '" does not exist.');
419
            }
420
            // Call the method
421
            $result = $this->$method($args);
422
        } else {
423
            // It's a function - does it exist?
424
            if (is_array($method)) {
425
                if (!method_exists($method[0], $method[1])) {
426
                    return new IXR_Error(-32601,
427
                                         'server error. requested object method "' . $method[1] . '" does not exist.');
428
                }
429
            } elseif (!function_exists($method)) {
430
                return new IXR_Error(-32601, 'server error. requested function "' . $method . '" does not exist.');
431
            }
432
            // Call the function
433
            $result = call_user_func($method, $args);
434
        }
435
436
        return $result;
437
    }
438
439
    /**
440
     * @param      $error
441
     * @param bool $message
442
     */
443
    public function error($error, $message = false) {
444
        // Accepts either an error object or an error code and message
445
        if ($message && !is_object($error)) {
446
            $error = new IXR_Error($error, $message);
447
        }
448
        $this->output($error->getXml());
449
    }
450
451
    /**
452
     * @param $xml
453
     */
454
    public function output($xml) {
455
        $xml    = '<?xml version="1.0"?>' . "\n" . $xml;
456
        $length = strlen($xml);
457
        header('Connection: close');
458
        header('Content-Length: ' . $length);
459
        header('Content-Type: text/xml');
460
        header('Date: ' . date('r'));
461
        echo $xml;
462
        exit;
463
    }
464
465
    /**
466
     * @param $method
467
     * @return bool
468
     */
469
    public function hasMethod($method) {
470
        return in_array($method, array_keys($this->callbacks));
471
    }
472
473
    public function setCapabilities() {
474
        // Initialises capabilities array
475
        $this->capabilities = array(
476
            'xmlrpc'           => array(
477
                'specUrl'     => 'http://www.xmlrpc.com/spec',
478
                'specVersion' => 1
479
            ),
480
            'faults_interop'   => array(
481
                'specUrl'     => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
482
                'specVersion' => 20010516
483
            ),
484
            'system.multicall' => array(
485
                'specUrl'     => 'http://www.xmlrpc.com/discuss/msgReader$1208',
486
                'specVersion' => 1
487
            )
488
        );
489
    }
490
491
    /**
492
     * @param $args
493
     * @return mixed
494
     */
495
    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...
496
        return $this->capabilities;
497
    }
498
499
    public function setCallbacks() {
500
        $this->callbacks['system.getCapabilities'] = 'this:getCapabilities';
501
        $this->callbacks['system.listMethods']     = 'this:listMethods';
502
        $this->callbacks['system.multicall']       = 'this:multiCall';
503
    }
504
505
    /**
506
     * @param $args
507
     * @return array
508
     */
509
    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...
510
        // Returns a list of methods - uses array_reverse to ensure user defined
511
        // methods are listed before server defined methods
512
        return array_reverse(array_keys($this->callbacks));
513
    }
514
515
    /**
516
     * @param $methodcalls
517
     * @return array
518
     */
519
    public function multiCall($methodcalls) {
520
        // See http://www.xmlrpc.com/discuss/msgReader$1208
521
        $return = array();
522
        foreach ($methodcalls as $call) {
523
            $method = $call['methodName'];
524
            $params = $call['params'];
525
            if ($method === 'system.multicall') {
526
                $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden');
527
            } else {
528
                $result = $this->call($method, $params);
529
            }
530
            if (is_a($result, 'IXR_Error')) {
531
                $return[] = array(
532
                    'faultCode'   => $result->code,
533
                    'faultString' => $result->message
534
                );
535
            } else {
536
                $return[] = array($result);
537
            }
538
        }
539
540
        return $return;
541
    }
542
}
543
544
/**
545
 * Class IXR_Request
546
 */
547
class IXR_Request
548
{
549
    public $method;
550
    public $args;
551
    public $xml;
552
553
    /**
554
     * IXR_Request constructor.
555
     * @param $method
556
     * @param $args
557
     */
558
    public function __construct($method, $args) {
559
        $this->method = $method;
560
        $this->args   = $args;
561
        $this->xml    = <<<EOD
562
<?xml version="1.0"?>
563
<methodCall>
564
<methodName>{$this->method}</methodName>
565
<params>
566
567
EOD;
568
        foreach ($this->args as $arg) {
569
            $this->xml .= '<param><value>';
570
            $v = new IXR_Value($arg);
571
            $this->xml .= $v->getXml();
572
            $this->xml .= "</value></param>\n";
573
        }
574
        $this->xml .= '</params></methodCall>';
575
    }
576
577
    /**
578
     * @return int
579
     */
580
    public function getLength() {
581
        return strlen($this->xml);
582
    }
583
584
    /**
585
     * @return string
586
     */
587
    public function getXml() {
588
        return $this->xml;
589
    }
590
}
591
592
/**
593
 * Class IXR_Client
594
 */
595
class IXR_Client
596
{
597
    public $server;
598
    public $port;
599
    public $path;
600
    public $useragent;
601
    public $response;
602
    public $timeout;
603
    public $vendor  = '';
604
    public $message = false;
605
    public $debug   = false;
606
    // Storage place for an error message
607
    public $error = false;
608
609
    /**
610
     * IXR_Client constructor.
611
     * @param        $server
612
     * @param bool   $path
613
     * @param int    $port
614
     * @param int    $timeout
615
     * @param string $vendor
616
     */
617
    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...
618
        if (!$path) {
619
            // Assume we have been given a URL instead
620
            $bits         = parse_url($server);
621
            $this->server = $bits['host'];
622
            $this->port   = isset($bits['port']) ? $bits['port'] : 80;
623
            $this->path   = isset($bits['path']) ? $bits['path'] : '/';
624
            // Make absolutely sure we have a path
625
            if (!$this->path) {
626
                $this->path = '/';
627
            }
628
        } else {
629
            $this->server  = $server;
630
            $this->path    = $path;
631
            $this->port    = $port;
632
            $this->timeout = $timeout;
633
        }
634
        $this->useragent = 'The Incutio XML-RPC PHP Library';
635
    }
636
637
    /**
638
     * @return bool
639
     */
640
    public function query() {
641
        $args    = func_get_args();
642
        $method  = array_shift($args);
643
        $request = new IXR_Request($method, $args);
644
        $length  = $request->getLength();
645
        $xml     = $request->getXml();
646
        $r       = "\r\n";
647
        $request = "POST {$this->path} HTTP/1.0$r";
648
        $request .= "Host: {$this->server}$r";
649
        $request .= "Content-Type: text/xml$r";
650
        $request .= "User-Agent: {$this->useragent}$r";
651
        $request .= "Content-length: {$length}$r$r";
652
        $request .= $xml;
653
        // Now send the request
654
        if ($this->debug) {
655
            echo '<pre>' . htmlspecialchars($request) . "\n</pre>\n\n";
656
        }
657
        $fp = @fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout);
658
        if (!$fp) {
659
            $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...
660
661
            return false;
662
        }
663
        fwrite($fp, $request);
664
        $contents       = '';
665
        $gotFirstLine   = false;
666
        $gettingHeaders = true;
667
        while (!feof($fp)) {
668
            $line = fgets($fp, 4096);
669
            if (!$gotFirstLine) {
670
                // Check line for '200'
671
                if (false === strpos($line, '200')) {
672
                    $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...
673
674
                    return false;
675
                }
676
                $gotFirstLine = true;
677
            }
678
            if (trim($line) == '') {
679
                $gettingHeaders = false;
680
            }
681
            if (!$gettingHeaders) {
682
                $contents .= trim($line) . "\n";
683
            }
684
        }
685
        if ($this->debug) {
686
            echo '<pre>' . htmlspecialchars($contents) . "\n</pre>\n\n";
687
        }
688
        // Now parse what we've got back
689
        $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...
690
        if (!$this->message->parse()) {
691
            // XML error
692
            $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...
693
694
            return false;
695
        }
696
        // Is the message a fault?
697
        if ($this->message->messageType === 'fault') {
698
            $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...
699
700
            return false;
701
        }
702
703
        // Message must be OK
704
        return true;
705
    }
706
707
    /**
708
     * @return mixed
709
     */
710
    public function getResponse() {
711
        // methodResponses can only have one param - return that
712
        return $this->message->params[0];
713
    }
714
715
    /**
716
     * @return bool
717
     */
718
    public function isError() {
719
        return is_object($this->error);
720
    }
721
722
    /**
723
     * @return mixed
724
     */
725
    public function getErrorCode() {
726
        return $this->error->code;
727
    }
728
729
    /**
730
     * @return mixed
731
     */
732
    public function getErrorMessage() {
733
        return $this->error->message;
734
    }
735
}
736
737
/**
738
 * Class IXR_Error
739
 */
740
class IXR_Error
741
{
742
    public $code;
743
    public $message;
744
745
    /**
746
     * IXR_Error constructor.
747
     * @param $code
748
     * @param $message
749
     */
750
    public function __construct($code, $message) {
751
        $this->code    = $code;
752
        $this->message = $message;
753
    }
754
755
    /**
756
     * @return string
757
     */
758
    public function getXml() {
759
        $xml = <<<EOD
760
<methodResponse>
761
  <fault>
762
    <value>
763
      <struct>
764
        <member>
765
          <name>faultCode</name>
766
          <value><int>{$this->code}</int></value>
767
        </member>
768
        <member>
769
          <name>faultString</name>
770
          <value><string>{$this->message}</string></value>
771
        </member>
772
      </struct>
773
    </value>
774
  </fault>
775
</methodResponse>
776
777
EOD;
778
779
        return $xml;
780
    }
781
}
782
783
/**
784
 * Class IXR_Date
785
 */
786
class IXR_Date
787
{
788
    public $year;
789
    public $month;
790
    public $day;
791
    public $hour;
792
    public $minute;
793
    public $second;
794
    public $timezone;
795
796
    /**
797
     * IXR_Date constructor.
798
     * @param $time
799
     */
800
    public function __construct($time) {
801
        // $time can be a PHP timestamp or an ISO one
802
        if (is_numeric($time)) {
803
            $this->parseTimestamp($time);
804
        } else {
805
            $this->parseIso($time);
806
        }
807
    }
808
809
    /**
810
     * @param $timestamp
811
     */
812
    public function parseTimestamp($timestamp) {
813
        $this->year   = date('Y', $timestamp);
814
        $this->month  = date('Y', $timestamp);
815
        $this->day    = date('Y', $timestamp);
816
        $this->hour   = date('H', $timestamp);
817
        $this->minute = date('i', $timestamp);
818
        $this->second = date('s', $timestamp);
819
    }
820
821
    /**
822
     * @param $iso
823
     */
824
    public function parseIso($iso) {
825
        $this->year     = substr($iso, 0, 4);
826
        $this->month    = substr($iso, 4, 2);
827
        $this->day      = substr($iso, 6, 2);
828
        $this->hour     = substr($iso, 9, 2);
829
        $this->minute   = substr($iso, 12, 2);
830
        $this->second   = substr($iso, 15, 2);
831
        $this->timezone = substr($iso, 17);
832
    }
833
834
    /**
835
     * @return string
836
     */
837
    public function getIso() {
838
        return $this->year . $this->month . $this->day . 'T' . $this->hour . ':' . $this->minute . ':' . $this->second
839
               . $this->timezone;
840
    }
841
842
    /**
843
     * @return string
844
     */
845
    public function getXml() {
846
        return '<dateTime.iso8601>' . $this->getIso() . '</dateTime.iso8601>';
847
    }
848
849
    /**
850
     * @return int
851
     */
852
    public function getTimestamp() {
853
        return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);
854
    }
855
}
856
857
/**
858
 * Class IXR_Base64
859
 */
860
class IXR_Base64
861
{
862
    public $data;
863
864
    /**
865
     * IXR_Base64 constructor.
866
     * @param $data
867
     */
868
    public function __construct($data) {
869
        $this->data = $data;
870
    }
871
872
    /**
873
     * @return string
874
     */
875
    public function getXml() {
876
        return '<base64>' . base64_encode($this->data) . '</base64>';
877
    }
878
}
879
880
/**
881
 * Class IXR_IntrospectionServer
882
 */
883
class IXR_IntrospectionServer extends IXR_Server
884
{
885
    public $signatures;
886
    public $help;
887
888
    /**
889
     * IXR_IntrospectionServer constructor.
890
     */
891
    public function __construct() {
892
        $this->setCallbacks();
893
        $this->setCapabilities();
894
        $this->capabilities['introspection'] = array(
895
            'specUrl'     => 'http://xmlrpc.usefulinc.com/doc/reserved.html',
896
            'specVersion' => 1
897
        );
898
        $this->addCallback('system.methodSignature', 'this:methodSignature', array('array', 'string'),
899
                           'Returns an array describing the return type and required parameters of a method');
900
        $this->addCallback('system.getCapabilities', 'this:getCapabilities', array('struct'),
901
                           'Returns a struct describing the XML-RPC specifications supported by this server');
902
        $this->addCallback('system.listMethods', 'this:listMethods', array('array'),
903
                           'Returns an array of available methods on this server');
904
        $this->addCallback('system.methodHelp', 'this:methodHelp', array('string', 'string'),
905
                           'Returns a documentation string for the specified method');
906
    }
907
908
    /**
909
     * @param $method
910
     * @param $callback
911
     * @param $args
912
     * @param $help
913
     */
914
    public function addCallback($method, $callback, $args, $help) {
915
        $this->callbacks[$method]  = $callback;
916
        $this->signatures[$method] = $args;
917
        $this->help[$method]       = $help;
918
    }
919
920
    /**
921
     * @param $methodname
922
     * @param $args
923
     * @return IXR_Error|mixed
924
     */
925
    public function call($methodname, $args) {
926
        // Make sure it's in an array
927
        if ($args && !is_array($args)) {
928
            $args = array($args);
929
        }
930
        // Over-rides default call method, adds signature check
931
        if (!$this->hasMethod($methodname)) {
932
            return new IXR_Error(-32601,
933
                                 'server error. requested method "' . $this->message->methodName . '" not specified.');
934
        }
935
        $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...
936
        $signature  = $this->signatures[$methodname];
937
        $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...
938
        // Check the number of arguments
939
        if (count($args) != count($signature)) {
940
            // 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...
941
            return new IXR_Error(-32602, 'server error. wrong number of method parameters');
942
        }
943
        // Check the argument types
944
        $ok         = true;
945
        $argsbackup = $args;
946
        for ($i = 0, $j = count($args); $i < $j; ++$i) {
947
            $arg  = array_shift($args);
948
            $type = array_shift($signature);
949
            switch ($type) {
950
                case 'int':
951
                case 'i4':
952
                    if (is_array($arg) || !is_int($arg)) {
953
                        $ok = false;
954
                    }
955
                    break;
956
                case 'base64':
957
                case 'string':
958
                    if (!is_string($arg)) {
959
                        $ok = false;
960
                    }
961
                    break;
962
                case 'boolean':
963
                    if ($arg !== false && $arg !== true) {
964
                        $ok = false;
965
                    }
966
                    break;
967
                case 'float':
968
                case 'double':
969
                    if (!is_float($arg)) {
970
                        $ok = false;
971
                    }
972
                    break;
973
                case 'date':
974
                case 'dateTime.iso8601':
975
                    if (!is_a($arg, 'IXR_Date')) {
976
                        $ok = false;
977
                    }
978
                    break;
979
            }
980
            if (!$ok) {
981
                return new IXR_Error(-32602, 'server error. invalid method parameters');
982
            }
983
        }
984
985
        // It passed the test - run the "real" method call
986
        return parent::call($methodname, $argsbackup);
987
    }
988
989
    /**
990
     * @param $method
991
     * @return array|IXR_Error
992
     */
993
    public function methodSignature($method) {
994
        if (!$this->hasMethod($method)) {
995
            return new IXR_Error(-32601, 'server error. requested method "' . $method . '" not specified.');
996
        }
997
        // We should be returning an array of types
998
        $types  = $this->signatures[$method];
999
        $return = array();
1000
        foreach ($types as $type) {
1001
            switch ($type) {
1002
                case 'string':
1003
                    $return[] = 'string';
1004
                    break;
1005
                case 'int':
1006
                case 'i4':
1007
                    $return[] = 42;
1008
                    break;
1009
                case 'double':
1010
                    $return[] = 3.1415;
1011
                    break;
1012
                case 'dateTime.iso8601':
1013
                    $return[] = new IXR_Date(time());
1014
                    break;
1015
                case 'boolean':
1016
                    $return[] = true;
1017
                    break;
1018
                case 'base64':
1019
                    $return[] = new IXR_Base64('base64');
1020
                    break;
1021
                case 'array':
1022
                    $return[] = array('array');
1023
                    break;
1024
                case 'struct':
1025
                    $return[] = array('struct' => 'struct');
1026
                    break;
1027
            }
1028
        }
1029
1030
        return $return;
1031
    }
1032
1033
    /**
1034
     * @param $method
1035
     * @return mixed
1036
     */
1037
    public function methodHelp($method) {
1038
        return $this->help[$method];
1039
    }
1040
}
1041
1042
/**
1043
 * Class IXR_ClientMulticall
1044
 */
1045
class IXR_ClientMulticall extends IXR_Client
1046
{
1047
    public $calls = array();
1048
1049
    /**
1050
     * IXR_ClientMulticall constructor.
1051
     * @param      $server
1052
     * @param bool $path
1053
     * @param int  $port
1054
     */
1055
    public function __construct($server, $path = false, $port = 80) {
1056
        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...
1057
        $this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)';
1058
    }
1059
1060
    public function addCall() {
1061
        $args          = func_get_args();
1062
        $methodName    = array_shift($args);
1063
        $struct        = array(
1064
            'methodName' => $methodName,
1065
            'params'     => $args
1066
        );
1067
        $this->calls[] = $struct;
1068
    }
1069
1070
    /**
1071
     * @return bool
1072
     */
1073
    public function query() {
1074
        // Prepare multicall, then call the parent::query() method
1075
        return parent::query('system.multicall', $this->calls);
1076
    }
1077
}
1078