~canonical-sysadmins/wordpress/4.7.2

« back to all changes in this revision

Viewing changes to wp-includes/class-IXR.php

  • Committer: Jacek Nykis
  • Date: 2015-01-05 16:17:05 UTC
  • Revision ID: jacek.nykis@canonical.com-20150105161705-w544l1h5mcg7u4w9
Initial commit

Show diffs side-by-side

added added

removed removed

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