Failed Conditions
Push — stable ( 017e16...b83837 )
by
unknown
10:45 queued 07:58
created

inc/IXR_Library.php (1 issue)

Labels
Severity

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) {
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
    const XMLRPC_ISO8601 = "Ymd\TH:i:sO" ;
826
    /** @var DateTime */
827
    protected $date;
828
829
    /**
830
     * @param int|string $time
831
     */
832
    public function __construct($time) {
833
        // $time can be a PHP timestamp or an ISO one
834
        if(is_numeric($time)) {
835
            $this->parseTimestamp($time);
836
        } else {
837
            $this->parseIso($time);
838
        }
839
    }
840
841
    /**
842
     * Parse unix timestamp
843
     *
844
     * @param int $timestamp
845
     */
846
    protected function parseTimestamp($timestamp) {
847
        $this->date = new DateTime('@' . $timestamp);
848
    }
849
850
    /**
851
     * Parses less or more complete iso dates and much more, if no timezone given assumes UTC
852
     *
853
     * @param string $iso
854
     */
855
    protected function parseIso($iso) {
856
        $this->date = new DateTime($iso, new DateTimeZone("UTC"));
857
    }
858
859
    /**
860
     * Returns date in ISO 8601 format
861
     *
862
     * @return string
863
     */
864
    public function getIso() {
865
	      return $this->date->format(self::XMLRPC_ISO8601);
866
    }
867
868
    /**
869
     * Returns date in valid xml
870
     *
871
     * @return string
872
     */
873
    public function getXml() {
874
        return '<dateTime.iso8601>' . $this->getIso() . '</dateTime.iso8601>';
875
    }
876
877
    /**
878
     * Returns Unix timestamp
879
     *
880
     * @return int
881
     */
882
    function getTimestamp() {
883
        return $this->date->getTimestamp();
884
    }
885
}
886
887
/**
888
 * IXR_Base64
889
 *
890
 * @package IXR
891
 * @since 1.5
892
 */
893
class IXR_Base64 {
894
    var $data;
895
896
    /**
897
     * @param string $data
898
     */
899
    function __construct($data) {
900
        $this->data = $data;
901
    }
902
903
    /**
904
     * @return string
905
     */
906
    function getXml() {
907
        return '<base64>' . base64_encode($this->data) . '</base64>';
908
    }
909
}
910
911
/**
912
 * IXR_IntrospectionServer
913
 *
914
 * @package IXR
915
 * @since 1.5
916
 */
917
class IXR_IntrospectionServer extends IXR_Server {
918
    /** @var array[] */
919
    var $signatures;
920
    /** @var string[] */
921
    var $help;
922
923
    /**
924
     * Constructor
925
     */
926
    function __construct() {
927
        $this->setCallbacks();
928
        $this->setCapabilities();
929
        $this->capabilities['introspection'] = array(
930
            'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html',
931
            'specVersion' => 1
932
        );
933
        $this->addCallback(
934
            'system.methodSignature',
935
            'this:methodSignature',
936
            array('array', 'string'),
937
            'Returns an array describing the return type and required parameters of a method'
938
        );
939
        $this->addCallback(
940
            'system.getCapabilities',
941
            'this:getCapabilities',
942
            array('struct'),
943
            'Returns a struct describing the XML-RPC specifications supported by this server'
944
        );
945
        $this->addCallback(
946
            'system.listMethods',
947
            'this:listMethods',
948
            array('array'),
949
            'Returns an array of available methods on this server'
950
        );
951
        $this->addCallback(
952
            'system.methodHelp',
953
            'this:methodHelp',
954
            array('string', 'string'),
955
            'Returns a documentation string for the specified method'
956
        );
957
    }
958
959
    /**
960
     * @param string $method
961
     * @param string $callback
962
     * @param string[] $args
963
     * @param string $help
964
     */
965
    function addCallback($method, $callback, $args, $help) {
966
        $this->callbacks[$method] = $callback;
967
        $this->signatures[$method] = $args;
968
        $this->help[$method] = $help;
969
    }
970
971
    /**
972
     * @param string $methodname
973
     * @param array $args
974
     * @return IXR_Error|mixed
975
     */
976
    function call($methodname, $args) {
977
        // Make sure it's in an array
978
        if($args && !is_array($args)) {
979
            $args = array($args);
980
        }
981
982
        // Over-rides default call method, adds signature check
983
        if(!$this->hasMethod($methodname)) {
984
            return new IXR_Error(-32601, 'server error. requested method "' . $this->message->methodName . '" not specified.');
985
        }
986
        $method = $this->callbacks[$methodname];
987
        $signature = $this->signatures[$methodname];
988
        $returnType = array_shift($signature);
989
        // Check the number of arguments. Check only, if the minimum count of parameters is specified. More parameters are possible.
990
        // This is a hack to allow optional parameters...
991
        if(count($args) < count($signature)) {
992
            // print 'Num of args: '.count($args).' Num in signature: '.count($signature);
993
            return new IXR_Error(-32602, 'server error. wrong number of method parameters');
994
        }
995
996
        // Check the argument types
997
        $ok = true;
998
        $argsbackup = $args;
999
        for($i = 0, $j = count($args); $i < $j; $i++) {
1000
            $arg = array_shift($args);
1001
            $type = array_shift($signature);
1002
            switch($type) {
1003
                case 'int':
1004
                case 'i4':
1005
                    if(is_array($arg) || !is_int($arg)) {
1006
                        $ok = false;
1007
                    }
1008
                    break;
1009
                case 'base64':
1010
                case 'string':
1011
                    if(!is_string($arg)) {
1012
                        $ok = false;
1013
                    }
1014
                    break;
1015
                case 'boolean':
1016
                    if($arg !== false && $arg !== true) {
1017
                        $ok = false;
1018
                    }
1019
                    break;
1020
                case 'float':
1021
                case 'double':
1022
                    if(!is_float($arg)) {
1023
                        $ok = false;
1024
                    }
1025
                    break;
1026
                case 'date':
1027
                case 'dateTime.iso8601':
1028
                    if(!is_a($arg, 'IXR_Date')) {
1029
                        $ok = false;
1030
                    }
1031
                    break;
1032
            }
1033
            if(!$ok) {
1034
                return new IXR_Error(-32602, 'server error. invalid method parameters');
1035
            }
1036
        }
1037
        // It passed the test - run the "real" method call
1038
        return parent::call($methodname, $argsbackup);
1039
    }
1040
1041
    /**
1042
     * @param string $method
1043
     * @return array|IXR_Error
1044
     */
1045
    function methodSignature($method) {
1046
        if(!$this->hasMethod($method)) {
1047
            return new IXR_Error(-32601, 'server error. requested method "' . $method . '" not specified.');
1048
        }
1049
        // We should be returning an array of types
1050
        $types = $this->signatures[$method];
1051
        $return = array();
1052
        foreach($types as $type) {
1053
            switch($type) {
1054
                case 'string':
1055
                    $return[] = 'string';
1056
                    break;
1057
                case 'int':
1058
                case 'i4':
1059
                    $return[] = 42;
1060
                    break;
1061
                case 'double':
1062
                    $return[] = 3.1415;
1063
                    break;
1064
                case 'dateTime.iso8601':
1065
                    $return[] = new IXR_Date(time());
1066
                    break;
1067
                case 'boolean':
1068
                    $return[] = true;
1069
                    break;
1070
                case 'base64':
1071
                    $return[] = new IXR_Base64('base64');
1072
                    break;
1073
                case 'array':
1074
                    $return[] = array('array');
1075
                    break;
1076
                case 'struct':
1077
                    $return[] = array('struct' => 'struct');
1078
                    break;
1079
            }
1080
        }
1081
        return $return;
1082
    }
1083
1084
    /**
1085
     * @param string $method
1086
     * @return mixed
1087
     */
1088
    function methodHelp($method) {
1089
        return $this->help[$method];
1090
    }
1091
}
1092
1093
/**
1094
 * IXR_ClientMulticall
1095
 *
1096
 * @package IXR
1097
 * @since 1.5
1098
 */
1099
class IXR_ClientMulticall extends IXR_Client {
1100
1101
    /** @var array[] */
1102
    var $calls = array();
1103
1104
    /**
1105
     * @param string $server
1106
     * @param string|bool $path
1107
     * @param int $port
1108
     */
1109
    function __construct($server, $path = false, $port = 80) {
1110
        parent::__construct($server, $path, $port);
1111
        //$this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)';
1112
    }
1113
1114
    /**
1115
     * Add a call
1116
     */
1117
    function addCall() {
1118
        $args = func_get_args();
1119
        $methodName = array_shift($args);
1120
        $struct = array(
1121
            'methodName' => $methodName,
1122
            'params' => $args
1123
        );
1124
        $this->calls[] = $struct;
1125
    }
1126
1127
    /**
1128
     * @return bool
1129
     */
1130
    function query() {
1131
        // Prepare multicall, then call the parent::query() method
1132
        return parent::query('system.multicall', $this->calls);
1133
    }
1134
}
1135
1136