Failed Conditions
Push — issue2517 ( 359362...5fdc2f )
by Henry
08:09 queued 05:10
created

inc/IXR_Library.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
use dokuwiki\HTTP\DokuHTTPClient;
0 ignored issues
show
This use statement conflicts with another class in this namespace, DokuHTTPClient.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
4
5
/**
6
 * IXR - The Incutio XML-RPC Library
7
 *
8
 * Copyright (c) 2010, Incutio Ltd.
9
 * All rights reserved.
10
 *
11
 * Redistribution and use in source and binary forms, with or without
12
 * modification, are permitted provided that the following conditions are met:
13
 *
14
 *  - Redistributions of source code must retain the above copyright notice,
15
 *    this list of conditions and the following disclaimer.
16
 *  - Redistributions in binary form must reproduce the above copyright
17
 *    notice, this list of conditions and the following disclaimer in the
18
 *    documentation and/or other materials provided with the distribution.
19
 *  - Neither the name of Incutio Ltd. nor the names of its contributors
20
 *    may be used to endorse or promote products derived from this software
21
 *    without specific prior written permission.
22
 *
23
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
24
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
25
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
26
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
27
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
28
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
29
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
31
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
33
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
 *
35
 * @package IXR
36
 * @since 1.5
37
 *
38
 * @copyright  Incutio Ltd 2010 (http://www.incutio.com)
39
 * @version    1.7.4 7th September 2010
40
 * @author     Simon Willison
41
 * @link       http://scripts.incutio.com/xmlrpc/ Site/manual
42
 *
43
 * Modified for DokuWiki
44
 * @author  Andreas Gohr <[email protected]>
45
 */
46
class IXR_Value {
47
48
    /** @var  IXR_Value[]|IXR_Date|IXR_Base64|int|bool|double|string */
49
    var $data;
50
    /** @var string */
51
    var $type;
52
53
    /**
54
     * @param mixed $data
55
     * @param bool $type
56
     */
57
    function __construct($data, $type = false) {
58
        $this->data = $data;
59
        if(!$type) {
60
            $type = $this->calculateType();
61
        }
62
        $this->type = $type;
63
        if($type == 'struct') {
64
            // Turn all the values in the array in to new IXR_Value objects
65
            foreach($this->data as $key => $value) {
66
                $this->data[$key] = new IXR_Value($value);
67
            }
68
        }
69
        if($type == 'array') {
70
            for($i = 0, $j = count($this->data); $i < $j; $i++) {
71
                $this->data[$i] = new IXR_Value($this->data[$i]);
72
            }
73
        }
74
    }
75
76
    /**
77
     * @return string
78
     */
79
    function calculateType() {
80
        if($this->data === true || $this->data === false) {
81
            return 'boolean';
82
        }
83
        if(is_integer($this->data)) {
84
            return 'int';
85
        }
86
        if(is_double($this->data)) {
87
            return 'double';
88
        }
89
90
        // Deal with IXR object types base64 and date
91
        if(is_object($this->data) && is_a($this->data, 'IXR_Date')) {
92
            return 'date';
93
        }
94
        if(is_object($this->data) && is_a($this->data, 'IXR_Base64')) {
95
            return 'base64';
96
        }
97
98
        // If it is a normal PHP object convert it in to a struct
99
        if(is_object($this->data)) {
100
            $this->data = get_object_vars($this->data);
101
            return 'struct';
102
        }
103
        if(!is_array($this->data)) {
104
            return 'string';
105
        }
106
107
        // We have an array - is it an array or a struct?
108
        if($this->isStruct($this->data)) {
109
            return 'struct';
110
        } else {
111
            return 'array';
112
        }
113
    }
114
115
    /**
116
     * @return bool|string
117
     */
118
    function getXml() {
119
        // Return XML for this value
120
        switch($this->type) {
121
            case 'boolean':
122
                return '<boolean>' . (($this->data) ? '1' : '0') . '</boolean>';
123
                break;
124
            case 'int':
125
                return '<int>' . $this->data . '</int>';
126
                break;
127
            case 'double':
128
                return '<double>' . $this->data . '</double>';
129
                break;
130
            case 'string':
131
                return '<string>' . htmlspecialchars($this->data) . '</string>';
132
                break;
133
            case 'array':
134
                $return = '<array><data>' . "\n";
135
                foreach($this->data as $item) {
136
                    $return .= '  <value>' . $item->getXml() . "</value>\n";
137
                }
138
                $return .= '</data></array>';
139
                return $return;
140
                break;
141
            case 'struct':
142
                $return = '<struct>' . "\n";
143
                foreach($this->data as $name => $value) {
144
                    $return .= "  <member><name>$name</name><value>";
145
                    $return .= $value->getXml() . "</value></member>\n";
146
                }
147
                $return .= '</struct>';
148
                return $return;
149
                break;
150
            case 'date':
151
            case 'base64':
152
                return $this->data->getXml();
153
                break;
154
        }
155
        return false;
156
    }
157
158
    /**
159
     * Checks whether or not the supplied array is a struct or not
160
     *
161
     * @param array $array
162
     * @return boolean
163
     */
164
    function isStruct($array) {
165
        $expected = 0;
166
        foreach($array as $key => $value) {
167
            if((string) $key != (string) $expected) {
168
                return true;
169
            }
170
            $expected++;
171
        }
172
        return false;
173
    }
174
}
175
176
/**
177
 * IXR_MESSAGE
178
 *
179
 * @package IXR
180
 * @since 1.5
181
 *
182
 */
183
class IXR_Message {
184
    var $message;
185
    var $messageType; // methodCall / methodResponse / fault
186
    var $faultCode;
187
    var $faultString;
188
    var $methodName;
189
    var $params;
190
191
    // Current variable stacks
192
    var $_arraystructs = array(); // The stack used to keep track of the current array/struct
193
    var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array
194
    var $_currentStructName = array(); // A stack as well
195
    var $_param;
196
    var $_value;
197
    var $_currentTag;
198
    var $_currentTagContents;
199
    var $_lastseen;
200
    // The XML parser
201
    var $_parser;
202
203
    /**
204
     * @param string $message
205
     */
206
    function __construct($message) {
207
        $this->message =& $message;
208
    }
209
210
    /**
211
     * @return bool
212
     */
213
    function parse() {
214
        // first remove the XML declaration
215
        // merged from WP #10698 - this method avoids the RAM usage of preg_replace on very large messages
216
        $header = preg_replace('/<\?xml.*?\?' . '>/', '', substr($this->message, 0, 100), 1);
217
        $this->message = substr_replace($this->message, $header, 0, 100);
218
219
        // workaround for a bug in PHP/libxml2, see http://bugs.php.net/bug.php?id=45996
220
        $this->message = str_replace('&lt;', '&#60;', $this->message);
221
        $this->message = str_replace('&gt;', '&#62;', $this->message);
222
        $this->message = str_replace('&amp;', '&#38;', $this->message);
223
        $this->message = str_replace('&apos;', '&#39;', $this->message);
224
        $this->message = str_replace('&quot;', '&#34;', $this->message);
225
        $this->message = str_replace("\x0b", ' ', $this->message); //vertical tab
226
        if(trim($this->message) == '') {
227
            return false;
228
        }
229
        $this->_parser = xml_parser_create();
230
        // Set XML parser to take the case of tags in to account
231
        xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
232
        // Set XML parser callback functions
233
        xml_set_object($this->_parser, $this);
234
        xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');
235
        xml_set_character_data_handler($this->_parser, 'cdata');
236
        $chunk_size = 262144; // 256Kb, parse in chunks to avoid the RAM usage on very large messages
237
        $final = false;
238
        do {
239
            if(strlen($this->message) <= $chunk_size) {
240
                $final = true;
241
            }
242
            $part = substr($this->message, 0, $chunk_size);
243
            $this->message = substr($this->message, $chunk_size);
244
            if(!xml_parse($this->_parser, $part, $final)) {
245
                return false;
246
            }
247
            if($final) {
248
                break;
249
            }
250
        } while(true);
251
        xml_parser_free($this->_parser);
252
253
        // Grab the error messages, if any
254
        if($this->messageType == 'fault') {
255
            $this->faultCode = $this->params[0]['faultCode'];
256
            $this->faultString = $this->params[0]['faultString'];
257
        }
258
        return true;
259
    }
260
261
    /**
262
     * @param $parser
263
     * @param string $tag
264
     * @param $attr
265
     */
266
    function tag_open($parser, $tag, $attr) {
267
        $this->_currentTagContents = '';
268
        $this->_currentTag = $tag;
269
270
        switch($tag) {
271
            case 'methodCall':
272
            case 'methodResponse':
273
            case 'fault':
274
                $this->messageType = $tag;
275
                break;
276
            /* Deal with stacks of arrays and structs */
277
            case 'data': // data is to all intents and purposes more interesting than array
278
                $this->_arraystructstypes[] = 'array';
279
                $this->_arraystructs[] = array();
280
                break;
281
            case 'struct':
282
                $this->_arraystructstypes[] = 'struct';
283
                $this->_arraystructs[] = array();
284
                break;
285
        }
286
        $this->_lastseen = $tag;
287
    }
288
289
    /**
290
     * @param $parser
291
     * @param string $cdata
292
     */
293
    function cdata($parser, $cdata) {
294
        $this->_currentTagContents .= $cdata;
295
    }
296
297
    /**
298
     * @param $parser
299
     * @param $tag
300
     */
301
    function tag_close($parser, $tag) {
302
        $value = null;
303
        $valueFlag = false;
304
        switch($tag) {
305
            case 'int':
306
            case 'i4':
307
                $value = (int) trim($this->_currentTagContents);
308
                $valueFlag = true;
309
                break;
310
            case 'double':
311
                $value = (double) trim($this->_currentTagContents);
312
                $valueFlag = true;
313
                break;
314
            case 'string':
315
                $value = (string) $this->_currentTagContents;
316
                $valueFlag = true;
317
                break;
318
            case 'dateTime.iso8601':
319
                $value = new IXR_Date(trim($this->_currentTagContents));
320
                $valueFlag = true;
321
                break;
322
            case 'value':
323
                // "If no type is indicated, the type is string."
324
                if($this->_lastseen == 'value') {
325
                    $value = (string) $this->_currentTagContents;
326
                    $valueFlag = true;
327
                }
328
                break;
329
            case 'boolean':
330
                $value = (boolean) trim($this->_currentTagContents);
331
                $valueFlag = true;
332
                break;
333
            case 'base64':
334
                $value = base64_decode($this->_currentTagContents);
335
                $valueFlag = true;
336
                break;
337
            /* Deal with stacks of arrays and structs */
338
            case 'data':
339
            case 'struct':
340
                $value = array_pop($this->_arraystructs);
341
                array_pop($this->_arraystructstypes);
342
                $valueFlag = true;
343
                break;
344
            case 'member':
345
                array_pop($this->_currentStructName);
346
                break;
347
            case 'name':
348
                $this->_currentStructName[] = trim($this->_currentTagContents);
349
                break;
350
            case 'methodName':
351
                $this->methodName = trim($this->_currentTagContents);
352
                break;
353
        }
354
355
        if($valueFlag) {
356
            if(count($this->_arraystructs) > 0) {
357
                // Add value to struct or array
358
                if($this->_arraystructstypes[count($this->_arraystructstypes) - 1] == 'struct') {
359
                    // Add to struct
360
                    $this->_arraystructs[count($this->_arraystructs) - 1][$this->_currentStructName[count($this->_currentStructName) - 1]] = $value;
361
                } else {
362
                    // Add to array
363
                    $this->_arraystructs[count($this->_arraystructs) - 1][] = $value;
364
                }
365
            } else {
366
                // Just add as a parameter
367
                $this->params[] = $value;
368
            }
369
        }
370
        $this->_currentTagContents = '';
371
        $this->_lastseen = $tag;
372
    }
373
}
374
375
/**
376
 * IXR_Server
377
 *
378
 * @package IXR
379
 * @since 1.5
380
 */
381
class IXR_Server {
382
    var $data;
383
    /** @var array */
384
    var $callbacks = array();
385
    var $message;
386
    /** @var array */
387
    var $capabilities;
388
389
    /**
390
     * @param array|bool $callbacks
391
     * @param bool $data
392
     * @param bool $wait
393
     */
394
    function __construct($callbacks = false, $data = false, $wait = false) {
395
        $this->setCapabilities();
396
        if($callbacks) {
397
            $this->callbacks = $callbacks;
398
        }
399
        $this->setCallbacks();
400
401
        if(!$wait) {
402
            $this->serve($data);
403
        }
404
    }
405
406
    /**
407
     * @param bool|string $data
408
     */
409
    function serve($data = false) {
410
        if(!$data) {
411
412
            $postData = trim(http_get_raw_post_data());
413
            if(!$postData) {
414
                header('Content-Type: text/plain'); // merged from WP #9093
415
                die('XML-RPC server accepts POST requests only.');
416
            }
417
            $data = $postData;
418
        }
419
        $this->message = new IXR_Message($data);
420
        if(!$this->message->parse()) {
421
            $this->error(-32700, 'parse error. not well formed');
422
        }
423
        if($this->message->messageType != 'methodCall') {
424
            $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall');
425
        }
426
        $result = $this->call($this->message->methodName, $this->message->params);
427
428
        // Is the result an error?
429
        if(is_a($result, 'IXR_Error')) {
430
            $this->error($result);
431
        }
432
433
        // Encode the result
434
        $r = new IXR_Value($result);
435
        $resultxml = $r->getXml();
436
437
        // Create the XML
438
        $xml = <<<EOD
439
<methodResponse>
440
  <params>
441
    <param>
442
      <value>
443
        $resultxml
444
      </value>
445
    </param>
446
  </params>
447
</methodResponse>
448
449
EOD;
450
        // Send it
451
        $this->output($xml);
452
    }
453
454
    /**
455
     * @param string $methodname
456
     * @param array $args
457
     * @return IXR_Error|mixed
458
     */
459
    function call($methodname, $args) {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
460
        if(!$this->hasMethod($methodname)) {
461
            return new IXR_Error(-32601, 'server error. requested method ' . $methodname . ' does not exist.');
462
        }
463
        $method = $this->callbacks[$methodname];
464
465
        // Perform the callback and send the response
466
467
        # Removed for DokuWiki to have a more consistent interface
468
        #        if (count($args) == 1) {
469
        #            // If only one parameter just send that instead of the whole array
470
        #            $args = $args[0];
471
        #        }
472
473
        # Adjusted for DokuWiki to use call_user_func_array
474
475
        // args need to be an array
476
        $args = (array) $args;
477
478
        // Are we dealing with a function or a method?
479
        if(is_string($method) && substr($method, 0, 5) == 'this:') {
480
            // It's a class method - check it exists
481
            $method = substr($method, 5);
482
            if(!method_exists($this, $method)) {
483
                return new IXR_Error(-32601, 'server error. requested class method "' . $method . '" does not exist.');
484
            }
485
            // Call the method
486
            #$result = $this->$method($args);
487
            $result = call_user_func_array(array(&$this, $method), $args);
488
        } elseif(substr($method, 0, 7) == 'plugin:') {
489
            list($pluginname, $callback) = explode(':', substr($method, 7), 2);
490
            if(!plugin_isdisabled($pluginname)) {
491
                $plugin = plugin_load('action', $pluginname);
492
                return call_user_func_array(array($plugin, $callback), $args);
493
            } else {
494
                return new IXR_Error(-99999, 'server error');
495
            }
496
        } else {
497
            // It's a function - does it exist?
498
            if(is_array($method)) {
499
                if(!is_callable(array($method[0], $method[1]))) {
500
                    return new IXR_Error(-32601, 'server error. requested object method "' . $method[1] . '" does not exist.');
501
                }
502
            } else if(!function_exists($method)) {
503
                return new IXR_Error(-32601, 'server error. requested function "' . $method . '" does not exist.');
504
            }
505
506
            // Call the function
507
            $result = call_user_func($method, $args);
508
        }
509
        return $result;
510
    }
511
512
    /**
513
     * @param int $error
514
     * @param string|bool $message
515
     */
516
    function error($error, $message = false) {
517
        // Accepts either an error object or an error code and message
518
        if($message && !is_object($error)) {
519
            $error = new IXR_Error($error, $message);
520
        }
521
        $this->output($error->getXml());
522
    }
523
524
    /**
525
     * @param string $xml
526
     */
527
    function output($xml) {
528
        header('Content-Type: text/xml; charset=utf-8');
529
        echo '<?xml version="1.0"?>', "\n", $xml;
530
        exit;
531
    }
532
533
    /**
534
     * @param string $method
535
     * @return bool
536
     */
537
    function hasMethod($method) {
538
        return in_array($method, array_keys($this->callbacks));
539
    }
540
541
    function setCapabilities() {
542
        // Initialises capabilities array
543
        $this->capabilities = array(
544
            'xmlrpc' => array(
545
                'specUrl' => 'http://www.xmlrpc.com/spec',
546
                'specVersion' => 1
547
            ),
548
            'faults_interop' => array(
549
                'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
550
                'specVersion' => 20010516
551
            ),
552
            'system.multicall' => array(
553
                'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
554
                'specVersion' => 1
555
            ),
556
        );
557
    }
558
559
    /**
560
     * @return mixed
561
     */
562
    function getCapabilities() {
563
        return $this->capabilities;
564
    }
565
566
    function setCallbacks() {
567
        $this->callbacks['system.getCapabilities'] = 'this:getCapabilities';
568
        $this->callbacks['system.listMethods'] = 'this:listMethods';
569
        $this->callbacks['system.multicall'] = 'this:multiCall';
570
    }
571
572
    /**
573
     * @return array
574
     */
575
    function listMethods() {
576
        // Returns a list of methods - uses array_reverse to ensure user defined
577
        // methods are listed before server defined methods
578
        return array_reverse(array_keys($this->callbacks));
579
    }
580
581
    /**
582
     * @param array $methodcalls
583
     * @return array
584
     */
585
    function multiCall($methodcalls) {
586
        // See http://www.xmlrpc.com/discuss/msgReader$1208
587
        $return = array();
588
        foreach($methodcalls as $call) {
589
            $method = $call['methodName'];
590
            $params = $call['params'];
591
            if($method == 'system.multicall') {
592
                $result = new IXR_Error(-32800, 'Recursive calls to system.multicall are forbidden');
593
            } else {
594
                $result = $this->call($method, $params);
595
            }
596
            if(is_a($result, 'IXR_Error')) {
597
                $return[] = array(
598
                    'faultCode' => $result->code,
599
                    'faultString' => $result->message
600
                );
601
            } else {
602
                $return[] = array($result);
603
            }
604
        }
605
        return $return;
606
    }
607
}
608
609
/**
610
 * IXR_Request
611
 *
612
 * @package IXR
613
 * @since 1.5
614
 */
615
class IXR_Request {
616
    /** @var string */
617
    var $method;
618
    /** @var array */
619
    var $args;
620
    /** @var string */
621
    var $xml;
622
623
    /**
624
     * @param string $method
625
     * @param array $args
626
     */
627
    function __construct($method, $args) {
628
        $this->method = $method;
629
        $this->args = $args;
630
        $this->xml = <<<EOD
631
<?xml version="1.0"?>
632
<methodCall>
633
<methodName>{$this->method}</methodName>
634
<params>
635
636
EOD;
637
        foreach($this->args as $arg) {
638
            $this->xml .= '<param><value>';
639
            $v = new IXR_Value($arg);
640
            $this->xml .= $v->getXml();
641
            $this->xml .= "</value></param>\n";
642
        }
643
        $this->xml .= '</params></methodCall>';
644
    }
645
646
    /**
647
     * @return int
648
     */
649
    function getLength() {
650
        return strlen($this->xml);
651
    }
652
653
    /**
654
     * @return string
655
     */
656
    function getXml() {
657
        return $this->xml;
658
    }
659
}
660
661
/**
662
 * IXR_Client
663
 *
664
 * @package IXR
665
 * @since 1.5
666
 *
667
 * Changed for DokuWiki to use DokuHTTPClient
668
 *
669
 * This should be compatible to the original class, but uses DokuWiki's
670
 * HTTP client library which will respect proxy settings
671
 *
672
 * Because the XMLRPC client is not used in DokuWiki currently this is completely
673
 * untested
674
 */
675
class IXR_Client extends DokuHTTPClient {
676
    var $posturl = '';
677
    /** @var IXR_Message|bool */
678
    var $message = false;
679
680
    // Storage place for an error message
681
    /** @var IXR_Error|bool */
682
    var $xmlerror = false;
683
684
    /**
685
     * @param string $server
686
     * @param string|bool $path
687
     * @param int $port
688
     * @param int $timeout
689
     */
690
    function __construct($server, $path = false, $port = 80, $timeout = 15) {
691
        parent::__construct();
692
        if(!$path) {
693
            // Assume we have been given a URL instead
694
            $this->posturl = $server;
695
        } else {
696
            $this->posturl = 'http://' . $server . ':' . $port . $path;
697
        }
698
        $this->timeout = $timeout;
699
    }
700
701
    /**
702
     * parameters: method and arguments
703
     * @return bool success or error
704
     */
705
    function query() {
706
        $args = func_get_args();
707
        $method = array_shift($args);
708
        $request = new IXR_Request($method, $args);
709
        $xml = $request->getXml();
710
711
        $this->headers['Content-Type'] = 'text/xml';
712
        if(!$this->sendRequest($this->posturl, $xml, 'POST')) {
713
            $this->xmlerror = new IXR_Error(-32300, 'transport error - ' . $this->error);
714
            return false;
715
        }
716
717
        // Check HTTP Response code
718
        if($this->status < 200 || $this->status > 206) {
719
            $this->xmlerror = new IXR_Error(-32300, 'transport error - HTTP status ' . $this->status);
720
            return false;
721
        }
722
723
        // Now parse what we've got back
724
        $this->message = new IXR_Message($this->resp_body);
725
        if(!$this->message->parse()) {
726
            // XML error
727
            $this->xmlerror = new IXR_Error(-32700, 'parse error. not well formed');
728
            return false;
729
        }
730
731
        // Is the message a fault?
732
        if($this->message->messageType == 'fault') {
733
            $this->xmlerror = new IXR_Error($this->message->faultCode, $this->message->faultString);
734
            return false;
735
        }
736
737
        // Message must be OK
738
        return true;
739
    }
740
741
    /**
742
     * @return mixed
743
     */
744
    function getResponse() {
745
        // methodResponses can only have one param - return that
746
        return $this->message->params[0];
747
    }
748
749
    /**
750
     * @return bool
751
     */
752
    function isError() {
753
        return (is_object($this->xmlerror));
754
    }
755
756
    /**
757
     * @return int
758
     */
759
    function getErrorCode() {
760
        return $this->xmlerror->code;
761
    }
762
763
    /**
764
     * @return string
765
     */
766
    function getErrorMessage() {
767
        return $this->xmlerror->message;
768
    }
769
}
770
771
/**
772
 * IXR_Error
773
 *
774
 * @package IXR
775
 * @since 1.5
776
 */
777
class IXR_Error {
778
    var $code;
779
    var $message;
780
781
    /**
782
     * @param int $code
783
     * @param string $message
784
     */
785
    function __construct($code, $message) {
786
        $this->code = $code;
787
        $this->message = htmlspecialchars($message);
788
    }
789
790
    /**
791
     * @return string
792
     */
793
    function getXml() {
794
        $xml = <<<EOD
795
<methodResponse>
796
  <fault>
797
    <value>
798
      <struct>
799
        <member>
800
          <name>faultCode</name>
801
          <value><int>{$this->code}</int></value>
802
        </member>
803
        <member>
804
          <name>faultString</name>
805
          <value><string>{$this->message}</string></value>
806
        </member>
807
      </struct>
808
    </value>
809
  </fault>
810
</methodResponse>
811
812
EOD;
813
        return $xml;
814
    }
815
}
816
817
/**
818
 * IXR_Date
819
 *
820
 * @package IXR
821
 * @since 1.5
822
 */
823
class IXR_Date {
824
825
    /** @var DateTime */
826
    protected $date;
827
828
    /**
829
     * @param int|string $time
830
     */
831
    public function __construct($time) {
832
        // $time can be a PHP timestamp or an ISO one
833
        if(is_numeric($time)) {
834
            $this->parseTimestamp($time);
835
        } else {
836
            $this->parseIso($time);
837
        }
838
    }
839
840
    /**
841
     * Parse unix timestamp
842
     *
843
     * @param int $timestamp
844
     */
845
    protected function parseTimestamp($timestamp) {
846
        $this->date = new DateTime('@' . $timestamp);
847
    }
848
849
    /**
850
     * Parses less or more complete iso dates and much more, if no timezone given assumes UTC
851
     *
852
     * @param string $iso
853
     */
854
    protected function parseIso($iso) {
855
        $this->date = new DateTime($iso, new DateTimeZone("UTC"));
856
    }
857
858
    /**
859
     * Returns date in ISO 8601 format
860
     *
861
     * @return string
862
     */
863
    public function getIso() {
864
        return $this->date->format(DateTime::ISO8601);
865
    }
866
867
    /**
868
     * Returns date in valid xml
869
     *
870
     * @return string
871
     */
872
    public function getXml() {
873
        return '<dateTime.iso8601>' . $this->getIso() . '</dateTime.iso8601>';
874
    }
875
876
    /**
877
     * Returns Unix timestamp
878
     *
879
     * @return int
880
     */
881
    function getTimestamp() {
882
        return $this->date->getTimestamp();
883
    }
884
}
885
886
/**
887
 * IXR_Base64
888
 *
889
 * @package IXR
890
 * @since 1.5
891
 */
892
class IXR_Base64 {
893
    var $data;
894
895
    /**
896
     * @param string $data
897
     */
898
    function __construct($data) {
899
        $this->data = $data;
900
    }
901
902
    /**
903
     * @return string
904
     */
905
    function getXml() {
906
        return '<base64>' . base64_encode($this->data) . '</base64>';
907
    }
908
}
909
910
/**
911
 * IXR_IntrospectionServer
912
 *
913
 * @package IXR
914
 * @since 1.5
915
 */
916
class IXR_IntrospectionServer extends IXR_Server {
917
    /** @var array[] */
918
    var $signatures;
919
    /** @var string[] */
920
    var $help;
921
922
    /**
923
     * Constructor
924
     */
925
    function __construct() {
926
        $this->setCallbacks();
927
        $this->setCapabilities();
928
        $this->capabilities['introspection'] = array(
929
            'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html',
930
            'specVersion' => 1
931
        );
932
        $this->addCallback(
933
            'system.methodSignature',
934
            'this:methodSignature',
935
            array('array', 'string'),
936
            'Returns an array describing the return type and required parameters of a method'
937
        );
938
        $this->addCallback(
939
            'system.getCapabilities',
940
            'this:getCapabilities',
941
            array('struct'),
942
            'Returns a struct describing the XML-RPC specifications supported by this server'
943
        );
944
        $this->addCallback(
945
            'system.listMethods',
946
            'this:listMethods',
947
            array('array'),
948
            'Returns an array of available methods on this server'
949
        );
950
        $this->addCallback(
951
            'system.methodHelp',
952
            'this:methodHelp',
953
            array('string', 'string'),
954
            'Returns a documentation string for the specified method'
955
        );
956
    }
957
958
    /**
959
     * @param string $method
960
     * @param string $callback
961
     * @param string[] $args
962
     * @param string $help
963
     */
964
    function addCallback($method, $callback, $args, $help) {
965
        $this->callbacks[$method] = $callback;
966
        $this->signatures[$method] = $args;
967
        $this->help[$method] = $help;
968
    }
969
970
    /**
971
     * @param string $methodname
972
     * @param array $args
973
     * @return IXR_Error|mixed
974
     */
975
    function call($methodname, $args) {
976
        // Make sure it's in an array
977
        if($args && !is_array($args)) {
978
            $args = array($args);
979
        }
980
981
        // Over-rides default call method, adds signature check
982
        if(!$this->hasMethod($methodname)) {
983
            return new IXR_Error(-32601, 'server error. requested method "' . $this->message->methodName . '" not specified.');
984
        }
985
        $method = $this->callbacks[$methodname];
986
        $signature = $this->signatures[$methodname];
987
        $returnType = array_shift($signature);
988
        // Check the number of arguments. Check only, if the minimum count of parameters is specified. More parameters are possible.
989
        // This is a hack to allow optional parameters...
990
        if(count($args) < count($signature)) {
991
            // print 'Num of args: '.count($args).' Num in signature: '.count($signature);
992
            return new IXR_Error(-32602, 'server error. wrong number of method parameters');
993
        }
994
995
        // Check the argument types
996
        $ok = true;
997
        $argsbackup = $args;
998
        for($i = 0, $j = count($args); $i < $j; $i++) {
999
            $arg = array_shift($args);
1000
            $type = array_shift($signature);
1001
            switch($type) {
1002
                case 'int':
1003
                case 'i4':
1004
                    if(is_array($arg) || !is_int($arg)) {
1005
                        $ok = false;
1006
                    }
1007
                    break;
1008
                case 'base64':
1009
                case 'string':
1010
                    if(!is_string($arg)) {
1011
                        $ok = false;
1012
                    }
1013
                    break;
1014
                case 'boolean':
1015
                    if($arg !== false && $arg !== true) {
1016
                        $ok = false;
1017
                    }
1018
                    break;
1019
                case 'float':
1020
                case 'double':
1021
                    if(!is_float($arg)) {
1022
                        $ok = false;
1023
                    }
1024
                    break;
1025
                case 'date':
1026
                case 'dateTime.iso8601':
1027
                    if(!is_a($arg, 'IXR_Date')) {
1028
                        $ok = false;
1029
                    }
1030
                    break;
1031
            }
1032
            if(!$ok) {
1033
                return new IXR_Error(-32602, 'server error. invalid method parameters');
1034
            }
1035
        }
1036
        // It passed the test - run the "real" method call
1037
        return parent::call($methodname, $argsbackup);
1038
    }
1039
1040
    /**
1041
     * @param string $method
1042
     * @return array|IXR_Error
1043
     */
1044
    function methodSignature($method) {
1045
        if(!$this->hasMethod($method)) {
1046
            return new IXR_Error(-32601, 'server error. requested method "' . $method . '" not specified.');
1047
        }
1048
        // We should be returning an array of types
1049
        $types = $this->signatures[$method];
1050
        $return = array();
1051
        foreach($types as $type) {
1052
            switch($type) {
1053
                case 'string':
1054
                    $return[] = 'string';
1055
                    break;
1056
                case 'int':
1057
                case 'i4':
1058
                    $return[] = 42;
1059
                    break;
1060
                case 'double':
1061
                    $return[] = 3.1415;
1062
                    break;
1063
                case 'dateTime.iso8601':
1064
                    $return[] = new IXR_Date(time());
1065
                    break;
1066
                case 'boolean':
1067
                    $return[] = true;
1068
                    break;
1069
                case 'base64':
1070
                    $return[] = new IXR_Base64('base64');
1071
                    break;
1072
                case 'array':
1073
                    $return[] = array('array');
1074
                    break;
1075
                case 'struct':
1076
                    $return[] = array('struct' => 'struct');
1077
                    break;
1078
            }
1079
        }
1080
        return $return;
1081
    }
1082
1083
    /**
1084
     * @param string $method
1085
     * @return mixed
1086
     */
1087
    function methodHelp($method) {
1088
        return $this->help[$method];
1089
    }
1090
}
1091
1092
/**
1093
 * IXR_ClientMulticall
1094
 *
1095
 * @package IXR
1096
 * @since 1.5
1097
 */
1098
class IXR_ClientMulticall extends IXR_Client {
1099
1100
    /** @var array[] */
1101
    var $calls = array();
1102
1103
    /**
1104
     * @param string $server
1105
     * @param string|bool $path
1106
     * @param int $port
1107
     */
1108
    function __construct($server, $path = false, $port = 80) {
1109
        parent::__construct($server, $path, $port);
1110
        //$this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)';
1111
    }
1112
1113
    /**
1114
     * Add a call
1115
     */
1116
    function addCall() {
1117
        $args = func_get_args();
1118
        $methodName = array_shift($args);
1119
        $struct = array(
1120
            'methodName' => $methodName,
1121
            'params' => $args
1122
        );
1123
        $this->calls[] = $struct;
1124
    }
1125
1126
    /**
1127
     * @return bool
1128
     */
1129
    function query() {
1130
        // Prepare multicall, then call the parent::query() method
1131
        return parent::query('system.multicall', $this->calls);
1132
    }
1133
}
1134
1135