PageRenderTime 37ms CodeModel.GetById 15ms app.highlight 18ms RepoModel.GetById 0ms app.codeStats 0ms

/Web/wp-includes/class-IXR.php

https://bitbucket.org/jimjenkins5/blog
PHP | 1063 lines | 812 code | 88 blank | 163 comment | 104 complexity | 1b1285d3ee550c83329c3fce5aa5d407 MD5 | raw file
Possible License(s): GPL-2.0, GPL-3.0, AGPL-1.0, LGPL-2.1
   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
  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
  47 */
  48class 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
 175 *
 176 */
 177class 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.*?\?'.'>/', '', substr($this->message, 0, 100), 1);
 207        $this->message = substr_replace($this->message, $header, 0, 100);
 208        if (trim($this->message) == '') {
 209            return false;
 210        }
 211        $this->_parser = xml_parser_create();
 212        // Set XML parser to take the case of tags in to account
 213        xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
 214        // Set XML parser callback functions
 215        xml_set_object($this->_parser, $this);
 216        xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');
 217        xml_set_character_data_handler($this->_parser, 'cdata');
 218        $chunk_size = 262144; // 256Kb, parse in chunks to avoid the RAM usage on very large messages
 219        $final = false;
 220        do {
 221            if (strlen($this->message) <= $chunk_size) {
 222                $final = true;
 223            }
 224            $part = substr($this->message, 0, $chunk_size);
 225            $this->message = substr($this->message, $chunk_size);
 226            if (!xml_parse($this->_parser, $part, $final)) {
 227                return false;
 228            }
 229            if ($final) {
 230                break;
 231            }
 232        } while (true);
 233        xml_parser_free($this->_parser);
 234
 235        // Grab the error messages, if any
 236        if ($this->messageType == 'fault') {
 237            $this->faultCode = $this->params[0]['faultCode'];
 238            $this->faultString = $this->params[0]['faultString'];
 239        }
 240        return true;
 241    }
 242
 243    function tag_open($parser, $tag, $attr)
 244    {
 245        $this->_currentTagContents = '';
 246        $this->currentTag = $tag;
 247        switch($tag) {
 248            case 'methodCall':
 249            case 'methodResponse':
 250            case 'fault':
 251                $this->messageType = $tag;
 252                break;
 253                /* Deal with stacks of arrays and structs */
 254            case 'data':    // data is to all intents and puposes more interesting than array
 255                $this->_arraystructstypes[] = 'array';
 256                $this->_arraystructs[] = array();
 257                break;
 258            case 'struct':
 259                $this->_arraystructstypes[] = 'struct';
 260                $this->_arraystructs[] = array();
 261                break;
 262        }
 263    }
 264
 265    function cdata($parser, $cdata)
 266    {
 267        $this->_currentTagContents .= $cdata;
 268    }
 269
 270    function tag_close($parser, $tag)
 271    {
 272        $valueFlag = false;
 273        switch($tag) {
 274            case 'int':
 275            case 'i4':
 276                $value = (int)trim($this->_currentTagContents);
 277                $valueFlag = true;
 278                break;
 279            case 'double':
 280                $value = (double)trim($this->_currentTagContents);
 281                $valueFlag = true;
 282                break;
 283            case 'string':
 284                $value = (string)trim($this->_currentTagContents);
 285                $valueFlag = true;
 286                break;
 287            case 'dateTime.iso8601':
 288                $value = new IXR_Date(trim($this->_currentTagContents));
 289                $valueFlag = true;
 290                break;
 291            case 'value':
 292                // "If no type is indicated, the type is string."
 293                if (trim($this->_currentTagContents) != '') {
 294                    $value = (string)$this->_currentTagContents;
 295                    $valueFlag = true;
 296                }
 297                break;
 298            case 'boolean':
 299                $value = (boolean)trim($this->_currentTagContents);
 300                $valueFlag = true;
 301                break;
 302            case 'base64':
 303                $value = base64_decode($this->_currentTagContents);
 304                $valueFlag = true;
 305                break;
 306                /* Deal with stacks of arrays and structs */
 307            case 'data':
 308            case 'struct':
 309                $value = array_pop($this->_arraystructs);
 310                array_pop($this->_arraystructstypes);
 311                $valueFlag = true;
 312                break;
 313            case 'member':
 314                array_pop($this->_currentStructName);
 315                break;
 316            case 'name':
 317                $this->_currentStructName[] = trim($this->_currentTagContents);
 318                break;
 319            case 'methodName':
 320                $this->methodName = trim($this->_currentTagContents);
 321                break;
 322        }
 323
 324        if ($valueFlag) {
 325            if (count($this->_arraystructs) > 0) {
 326                // Add value to struct or array
 327                if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {
 328                    // Add to struct
 329                    $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value;
 330                } else {
 331                    // Add to array
 332                    $this->_arraystructs[count($this->_arraystructs)-1][] = $value;
 333                }
 334            } else {
 335                // Just add as a paramater
 336                $this->params[] = $value;
 337            }
 338        }
 339        $this->_currentTagContents = '';
 340    }
 341}
 342
 343/**
 344 * IXR_Server
 345 *
 346 * @package IXR
 347 * @since 1.5
 348 */
 349class IXR_Server
 350{
 351    var $data;
 352    var $callbacks = array();
 353    var $message;
 354    var $capabilities;
 355
 356    function IXR_Server($callbacks = false, $data = false, $wait = false)
 357    {
 358        $this->setCapabilities();
 359        if ($callbacks) {
 360            $this->callbacks = $callbacks;
 361        }
 362        $this->setCallbacks();
 363        if (!$wait) {
 364            $this->serve($data);
 365        }
 366    }
 367
 368    function serve($data = false)
 369    {
 370        if (!$data) {
 371            if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] !== 'POST') {
 372            	header('Content-Type: text/plain'); // merged from WP #9093
 373                die('XML-RPC server accepts POST requests only.');
 374            }
 375
 376            global $HTTP_RAW_POST_DATA;
 377            if (empty($HTTP_RAW_POST_DATA)) {
 378                // workaround for a bug in PHP 5.2.2 - http://bugs.php.net/bug.php?id=41293
 379                $data = file_get_contents('php://input');
 380            } else {
 381                $data =& $HTTP_RAW_POST_DATA;
 382            }
 383        }
 384        $this->message = new IXR_Message($data);
 385        if (!$this->message->parse()) {
 386            $this->error(-32700, 'parse error. not well formed');
 387        }
 388        if ($this->message->messageType != 'methodCall') {
 389            $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall');
 390        }
 391        $result = $this->call($this->message->methodName, $this->message->params);
 392
 393        // Is the result an error?
 394        if (is_a($result, 'IXR_Error')) {
 395            $this->error($result);
 396        }
 397
 398        // Encode the result
 399        $r = new IXR_Value($result);
 400        $resultxml = $r->getXml();
 401
 402        // Create the XML
 403        $xml = <<<EOD
 404<methodResponse>
 405  <params>
 406    <param>
 407      <value>
 408      $resultxml
 409      </value>
 410    </param>
 411  </params>
 412</methodResponse>
 413
 414EOD;
 415      // Send it
 416      $this->output($xml);
 417    }
 418
 419    function call($methodname, $args)
 420    {
 421        if (!$this->hasMethod($methodname)) {
 422            return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.');
 423        }
 424        $method = $this->callbacks[$methodname];
 425
 426        // Perform the callback and send the response
 427        if (count($args) == 1) {
 428            // If only one paramater just send that instead of the whole array
 429            $args = $args[0];
 430        }
 431
 432        // Are we dealing with a function or a method?
 433        if (is_string($method) && substr($method, 0, 5) == 'this:') {
 434            // It's a class method - check it exists
 435            $method = substr($method, 5);
 436            if (!method_exists($this, $method)) {
 437                return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.');
 438            }
 439
 440            //Call the method
 441            $result = $this->$method($args);
 442        } else {
 443            // It's a function - does it exist?
 444            if (is_array($method)) {
 445                if (!is_callable(array($method[0], $method[1]))) {
 446                    return new IXR_Error(-32601, 'server error. requested object method "'.$method[1].'" does not exist.');
 447                }
 448            } else if (!function_exists($method)) {
 449                return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.');
 450            }
 451
 452            // Call the function
 453            $result = call_user_func($method, $args);
 454        }
 455        return $result;
 456    }
 457
 458    function error($error, $message = false)
 459    {
 460        // Accepts either an error object or an error code and message
 461        if ($message && !is_object($error)) {
 462            $error = new IXR_Error($error, $message);
 463        }
 464        $this->output($error->getXml());
 465    }
 466
 467    function output($xml)
 468    {
 469        $xml = '<?xml version="1.0"?>'."\n".$xml;
 470        $length = strlen($xml);
 471        header('Connection: close');
 472        header('Content-Length: '.$length);
 473        header('Content-Type: text/xml');
 474        header('Date: '.date('r'));
 475        echo $xml;
 476        exit;
 477    }
 478
 479    function hasMethod($method)
 480    {
 481        return in_array($method, array_keys($this->callbacks));
 482    }
 483
 484    function setCapabilities()
 485    {
 486        // Initialises capabilities array
 487        $this->capabilities = array(
 488            'xmlrpc' => array(
 489                'specUrl' => 'http://www.xmlrpc.com/spec',
 490                'specVersion' => 1
 491        ),
 492            'faults_interop' => array(
 493                'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
 494                'specVersion' => 20010516
 495        ),
 496            'system.multicall' => array(
 497                'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
 498                'specVersion' => 1
 499        ),
 500        );
 501    }
 502
 503    function getCapabilities($args)
 504    {
 505        return $this->capabilities;
 506    }
 507
 508    function setCallbacks()
 509    {
 510        $this->callbacks['system.getCapabilities'] = 'this:getCapabilities';
 511        $this->callbacks['system.listMethods'] = 'this:listMethods';
 512        $this->callbacks['system.multicall'] = 'this:multiCall';
 513    }
 514
 515    function listMethods($args)
 516    {
 517        // Returns a list of methods - uses array_reverse to ensure user defined
 518        // methods are listed before server defined methods
 519        return array_reverse(array_keys($this->callbacks));
 520    }
 521
 522    function multiCall($methodcalls)
 523    {
 524        // See http://www.xmlrpc.com/discuss/msgReader$1208
 525        $return = array();
 526        foreach ($methodcalls as $call) {
 527            $method = $call['methodName'];
 528            $params = $call['params'];
 529            if ($method == 'system.multicall') {
 530                $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden');
 531            } else {
 532                $result = $this->call($method, $params);
 533            }
 534            if (is_a($result, 'IXR_Error')) {
 535                $return[] = array(
 536                    'faultCode' => $result->code,
 537                    'faultString' => $result->message
 538                );
 539            } else {
 540                $return[] = array($result);
 541            }
 542        }
 543        return $return;
 544    }
 545}
 546
 547/**
 548 * IXR_Request
 549 *
 550 * @package IXR
 551 * @since 1.5
 552 */
 553class IXR_Request
 554{
 555    var $method;
 556    var $args;
 557    var $xml;
 558
 559    function IXR_Request($method, $args)
 560    {
 561        $this->method = $method;
 562        $this->args = $args;
 563        $this->xml = <<<EOD
 564<?xml version="1.0"?>
 565<methodCall>
 566<methodName>{$this->method}</methodName>
 567<params>
 568
 569EOD;
 570        foreach ($this->args as $arg) {
 571            $this->xml .= '<param><value>';
 572            $v = new IXR_Value($arg);
 573            $this->xml .= $v->getXml();
 574            $this->xml .= "</value></param>\n";
 575        }
 576        $this->xml .= '</params></methodCall>';
 577    }
 578
 579    function getLength()
 580    {
 581        return strlen($this->xml);
 582    }
 583
 584    function getXml()
 585    {
 586        return $this->xml;
 587    }
 588}
 589
 590/**
 591 * IXR_Client
 592 *
 593 * @package IXR
 594 * @since 1.5
 595 *
 596 */
 597class IXR_Client
 598{
 599    var $server;
 600    var $port;
 601    var $path;
 602    var $useragent;
 603    var $response;
 604    var $message = false;
 605    var $debug = false;
 606    var $timeout;
 607    var $headers = array();
 608
 609    // Storage place for an error message
 610    var $error = false;
 611
 612    function IXR_Client($server, $path = false, $port = 80, $timeout = 15)
 613    {
 614        if (!$path) {
 615            // Assume we have been given a URL instead
 616            $bits = parse_url($server);
 617            $this->server = $bits['host'];
 618            $this->port = isset($bits['port']) ? $bits['port'] : 80;
 619            $this->path = isset($bits['path']) ? $bits['path'] : '/';
 620
 621            // Make absolutely sure we have a path
 622            if (!$this->path) {
 623                $this->path = '/';
 624            }
 625        } else {
 626            $this->server = $server;
 627            $this->path = $path;
 628            $this->port = $port;
 629        }
 630        $this->useragent = 'The Incutio XML-RPC PHP Library';
 631        $this->timeout = $timeout;
 632    }
 633
 634    function query()
 635    {
 636        $args = func_get_args();
 637        $method = array_shift($args);
 638        $request = new IXR_Request($method, $args);
 639        $length = $request->getLength();
 640        $xml = $request->getXml();
 641        $r = "\r\n";
 642        $request  = "POST {$this->path} HTTP/1.0$r";
 643
 644        // Merged from WP #8145 - allow custom headers
 645        $this->headers['Host']          = $this->server;
 646        $this->headers['Content-Type']  = 'text/xml';
 647        $this->headers['User-Agent']    = $this->useragent;
 648        $this->headers['Content-Length']= $length;
 649
 650        foreach( $this->headers as $header => $value ) {
 651            $request .= "{$header}: {$value}{$r}";
 652        }
 653        $request .= $r;
 654
 655        $request .= $xml;
 656
 657        // Now send the request
 658        if ($this->debug) {
 659            echo '<pre class="ixr_request">'.htmlspecialchars($request)."\n</pre>\n\n";
 660        }
 661
 662        if ($this->timeout) {
 663            $fp = @fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout);
 664        } else {
 665            $fp = @fsockopen($this->server, $this->port, $errno, $errstr);
 666        }
 667        if (!$fp) {
 668            $this->error = new IXR_Error(-32300, 'transport error - could not open socket');
 669            return false;
 670        }
 671        fputs($fp, $request);
 672        $contents = '';
 673        $debugContents = '';
 674        $gotFirstLine = false;
 675        $gettingHeaders = true;
 676        while (!feof($fp)) {
 677            $line = fgets($fp, 4096);
 678            if (!$gotFirstLine) {
 679                // Check line for '200'
 680                if (strstr($line, '200') === false) {
 681                    $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200');
 682                    return false;
 683                }
 684                $gotFirstLine = true;
 685            }
 686            if (trim($line) == '') {
 687                $gettingHeaders = false;
 688            }
 689            if (!$gettingHeaders) {
 690            	// merged from WP #12559 - remove trim
 691                $contents .= $line;
 692            }
 693            if ($this->debug) {
 694            	$debugContents .= $line;
 695            }
 696        }
 697        if ($this->debug) {
 698            echo '<pre class="ixr_response">'.htmlspecialchars($debugContents)."\n</pre>\n\n";
 699        }
 700
 701        // Now parse what we've got back
 702        $this->message = new IXR_Message($contents);
 703        if (!$this->message->parse()) {
 704            // XML error
 705            $this->error = new IXR_Error(-32700, 'parse error. not well formed');
 706            return false;
 707        }
 708
 709        // Is the message a fault?
 710        if ($this->message->messageType == 'fault') {
 711            $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);
 712            return false;
 713        }
 714
 715        // Message must be OK
 716        return true;
 717    }
 718
 719    function getResponse()
 720    {
 721        // methodResponses can only have one param - return that
 722        return $this->message->params[0];
 723    }
 724
 725    function isError()
 726    {
 727        return (is_object($this->error));
 728    }
 729
 730    function getErrorCode()
 731    {
 732        return $this->error->code;
 733    }
 734
 735    function getErrorMessage()
 736    {
 737        return $this->error->message;
 738    }
 739}
 740
 741
 742/**
 743 * IXR_Error
 744 *
 745 * @package IXR
 746 * @since 1.5
 747 */
 748class IXR_Error
 749{
 750    var $code;
 751    var $message;
 752
 753    function IXR_Error($code, $message)
 754    {
 755        $this->code = $code;
 756        $this->message = htmlspecialchars($message);
 757    }
 758
 759    function getXml()
 760    {
 761        $xml = <<<EOD
 762<methodResponse>
 763  <fault>
 764    <value>
 765      <struct>
 766        <member>
 767          <name>faultCode</name>
 768          <value><int>{$this->code}</int></value>
 769        </member>
 770        <member>
 771          <name>faultString</name>
 772          <value><string>{$this->message}</string></value>
 773        </member>
 774      </struct>
 775    </value>
 776  </fault>
 777</methodResponse>
 778
 779EOD;
 780        return $xml;
 781    }
 782}
 783
 784/**
 785 * IXR_Date
 786 *
 787 * @package IXR
 788 * @since 1.5
 789 */
 790class IXR_Date {
 791    var $year;
 792    var $month;
 793    var $day;
 794    var $hour;
 795    var $minute;
 796    var $second;
 797    var $timezone;
 798
 799    function IXR_Date($time)
 800    {
 801        // $time can be a PHP timestamp or an ISO one
 802        if (is_numeric($time)) {
 803            $this->parseTimestamp($time);
 804        } else {
 805            $this->parseIso($time);
 806        }
 807    }
 808
 809    function parseTimestamp($timestamp)
 810    {
 811        $this->year = date('Y', $timestamp);
 812        $this->month = date('m', $timestamp);
 813        $this->day = date('d', $timestamp);
 814        $this->hour = date('H', $timestamp);
 815        $this->minute = date('i', $timestamp);
 816        $this->second = date('s', $timestamp);
 817        $this->timezone = '';
 818    }
 819
 820    function parseIso($iso)
 821    {
 822        $this->year = substr($iso, 0, 4);
 823        $this->month = substr($iso, 4, 2);
 824        $this->day = substr($iso, 6, 2);
 825        $this->hour = substr($iso, 9, 2);
 826        $this->minute = substr($iso, 12, 2);
 827        $this->second = substr($iso, 15, 2);
 828        $this->timezone = substr($iso, 17);
 829    }
 830
 831    function getIso()
 832    {
 833        return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second.$this->timezone;
 834    }
 835
 836    function getXml()
 837    {
 838        return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>';
 839    }
 840
 841    function getTimestamp()
 842    {
 843        return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);
 844    }
 845}
 846
 847/**
 848 * IXR_Base64
 849 *
 850 * @package IXR
 851 * @since 1.5
 852 */
 853class IXR_Base64
 854{
 855    var $data;
 856
 857    function IXR_Base64($data)
 858    {
 859        $this->data = $data;
 860    }
 861
 862    function getXml()
 863    {
 864        return '<base64>'.base64_encode($this->data).'</base64>';
 865    }
 866}
 867
 868/**
 869 * IXR_IntrospectionServer
 870 *
 871 * @package IXR
 872 * @since 1.5
 873 */
 874class IXR_IntrospectionServer extends IXR_Server
 875{
 876    var $signatures;
 877    var $help;
 878
 879    function IXR_IntrospectionServer()
 880    {
 881        $this->setCallbacks();
 882        $this->setCapabilities();
 883        $this->capabilities['introspection'] = array(
 884            'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html',
 885            'specVersion' => 1
 886        );
 887        $this->addCallback(
 888            'system.methodSignature',
 889            'this:methodSignature',
 890            array('array', 'string'),
 891            'Returns an array describing the return type and required parameters of a method'
 892        );
 893        $this->addCallback(
 894            'system.getCapabilities',
 895            'this:getCapabilities',
 896            array('struct'),
 897            'Returns a struct describing the XML-RPC specifications supported by this server'
 898        );
 899        $this->addCallback(
 900            'system.listMethods',
 901            'this:listMethods',
 902            array('array'),
 903            'Returns an array of available methods on this server'
 904        );
 905        $this->addCallback(
 906            'system.methodHelp',
 907            'this:methodHelp',
 908            array('string', 'string'),
 909            'Returns a documentation string for the specified method'
 910        );
 911    }
 912
 913    function addCallback($method, $callback, $args, $help)
 914    {
 915        $this->callbacks[$method] = $callback;
 916        $this->signatures[$method] = $args;
 917        $this->help[$method] = $help;
 918    }
 919
 920    function call($methodname, $args)
 921    {
 922        // Make sure it's in an array
 923        if ($args && !is_array($args)) {
 924            $args = array($args);
 925        }
 926
 927        // Over-rides default call method, adds signature check
 928        if (!$this->hasMethod($methodname)) {
 929            return new IXR_Error(-32601, 'server error. requested method "'.$this->message->methodName.'" not specified.');
 930        }
 931        $method = $this->callbacks[$methodname];
 932        $signature = $this->signatures[$methodname];
 933        $returnType = array_shift($signature);
 934
 935        // Check the number of arguments
 936        if (count($args) != count($signature)) {
 937            return new IXR_Error(-32602, 'server error. wrong number of method parameters');
 938        }
 939
 940        // Check the argument types
 941        $ok = true;
 942        $argsbackup = $args;
 943        for ($i = 0, $j = count($args); $i < $j; $i++) {
 944            $arg = array_shift($args);
 945            $type = array_shift($signature);
 946            switch ($type) {
 947                case 'int':
 948                case 'i4':
 949                    if (is_array($arg) || !is_int($arg)) {
 950                        $ok = false;
 951                    }
 952                    break;
 953                case 'base64':
 954                case 'string':
 955                    if (!is_string($arg)) {
 956                        $ok = false;
 957                    }
 958                    break;
 959                case 'boolean':
 960                    if ($arg !== false && $arg !== true) {
 961                        $ok = false;
 962                    }
 963                    break;
 964                case 'float':
 965                case 'double':
 966                    if (!is_float($arg)) {
 967                        $ok = false;
 968                    }
 969                    break;
 970                case 'date':
 971                case 'dateTime.iso8601':
 972                    if (!is_a($arg, 'IXR_Date')) {
 973                        $ok = false;
 974                    }
 975                    break;
 976            }
 977            if (!$ok) {
 978                return new IXR_Error(-32602, 'server error. invalid method parameters');
 979            }
 980        }
 981        // It passed the test - run the "real" method call
 982        return parent::call($methodname, $argsbackup);
 983    }
 984
 985    function methodSignature($method)
 986    {
 987        if (!$this->hasMethod($method)) {
 988            return new IXR_Error(-32601, 'server error. requested method "'.$method.'" not specified.');
 989        }
 990        // We should be returning an array of types
 991        $types = $this->signatures[$method];
 992        $return = array();
 993        foreach ($types as $type) {
 994            switch ($type) {
 995                case 'string':
 996                    $return[] = 'string';
 997                    break;
 998                case 'int':
 999                case 'i4':
1000                    $return[] = 42;
1001                    break;
1002                case 'double':
1003                    $return[] = 3.1415;
1004                    break;
1005                case 'dateTime.iso8601':
1006                    $return[] = new IXR_Date(time());
1007                    break;
1008                case 'boolean':
1009                    $return[] = true;
1010                    break;
1011                case 'base64':
1012                    $return[] = new IXR_Base64('base64');
1013                    break;
1014                case 'array':
1015                    $return[] = array('array');
1016                    break;
1017                case 'struct':
1018                    $return[] = array('struct' => 'struct');
1019                    break;
1020            }
1021        }
1022        return $return;
1023    }
1024
1025    function methodHelp($method)
1026    {
1027        return $this->help[$method];
1028    }
1029}
1030
1031/**
1032 * IXR_ClientMulticall
1033 *
1034 * @package IXR
1035 * @since 1.5
1036 */
1037class IXR_ClientMulticall extends IXR_Client
1038{
1039    var $calls = array();
1040
1041    function IXR_ClientMulticall($server, $path = false, $port = 80)
1042    {
1043        parent::IXR_Client($server, $path, $port);
1044        $this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)';
1045    }
1046
1047    function addCall()
1048    {
1049        $args = func_get_args();
1050        $methodName = array_shift($args);
1051        $struct = array(
1052            'methodName' => $methodName,
1053            'params' => $args
1054        );
1055        $this->calls[] = $struct;
1056    }
1057
1058    function query()
1059    {
1060        // Prepare multicall, then call the parent::query() method
1061        return parent::query('system.multicall', $this->calls);
1062    }
1063}