PageRenderTime 185ms CodeModel.GetById 80ms app.highlight 77ms RepoModel.GetById 14ms app.codeStats 1ms

/lib/external/php-ixr/IXR_Library.php

https://bitbucket.org/navigatecms/navigatecms
PHP | 1431 lines | 1025 code | 139 blank | 267 comment | 133 complexity | 0afb36133502b154214dc8cac51c4167 MD5 | raw file
   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 */
  40
  41class IXR_Value
  42{
  43    var $data;
  44    var $type;
  45
  46    function __construct($data, $type = false)
  47    {
  48        $this->IXR_Value($data, $type);
  49    }
  50
  51    function IXR_Value($data, $type = false)
  52    {
  53        $this->data = $data;
  54        if (!$type) {
  55            $type = $this->calculateType();
  56        }
  57        $this->type = $type;
  58        if ($type == 'struct') {
  59            // Turn all the values in the array in to new IXR_Value objects
  60            foreach ($this->data as $key => $value) {
  61                $this->data[$key] = new IXR_Value($value);
  62            }
  63        }
  64        if ($type == 'array') {
  65            for ($i = 0, $j = count($this->data); $i < $j; $i++) {
  66                $this->data[$i] = new IXR_Value($this->data[$i]);
  67            }
  68        }
  69    }
  70
  71    function calculateType()
  72    {
  73        if ($this->data === true || $this->data === false) {
  74            return 'boolean';
  75        }
  76        if (is_integer($this->data)) {
  77            return 'int';
  78        }
  79        if (is_double($this->data)) {
  80            return 'double';
  81        }
  82
  83        // Deal with IXR object types base64 and date
  84        if (is_object($this->data) && is_a($this->data, 'IXR_Date')) {
  85            return 'date';
  86        }
  87        if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) {
  88            return 'base64';
  89        }
  90
  91        // If it is a normal PHP object convert it in to a struct
  92        if (is_object($this->data)) {
  93            $this->data = get_object_vars($this->data);
  94            return 'struct';
  95        }
  96        if (!is_array($this->data)) {
  97            return 'string';
  98        }
  99
 100        // We have an array - is it an array or a struct?
 101        if ($this->isStruct($this->data)) {
 102            return 'struct';
 103        } else {
 104            return 'array';
 105        }
 106    }
 107
 108    function getXml()
 109    {
 110        // Return XML for this value
 111        switch ($this->type) {
 112            case 'boolean':
 113                return '<boolean>'.(($this->data) ? '1' : '0').'</boolean>';
 114                break;
 115            case 'int':
 116                return '<int>'.$this->data.'</int>';
 117                break;
 118            case 'double':
 119                return '<double>'.$this->data.'</double>';
 120                break;
 121            case 'string':
 122                return '<string>'.htmlspecialchars($this->data).'</string>';
 123                break;
 124            case 'array':
 125                $return = '<array><data>'."\n";
 126                foreach ($this->data as $item) {
 127                    $return .= '  <value>'.$item->getXml()."</value>\n";
 128                }
 129                $return .= '</data></array>';
 130                return $return;
 131                break;
 132            case 'struct':
 133                $return = '<struct>'."\n";
 134                foreach ($this->data as $name => $value) {
 135                    $return .= "  <member><name>$name</name><value>";
 136                    $return .= $value->getXml()."</value></member>\n";
 137                }
 138                $return .= '</struct>';
 139                return $return;
 140                break;
 141            case 'date':
 142            case 'base64':
 143                return $this->data->getXml();
 144                break;
 145        }
 146        return false;
 147    }
 148
 149    /**
 150     * Checks whether or not the supplied array is a struct or not
 151     *
 152     * @param unknown_type $array
 153     * @return boolean
 154     */
 155    function isStruct($array)
 156    {
 157        $expected = 0;
 158        foreach ($array as $key => $value) {
 159            if ((string)$key != (string)$expected) {
 160                return true;
 161            }
 162            $expected++;
 163        }
 164        return false;
 165    }
 166}
 167
 168/**
 169 * IXR_MESSAGE
 170 *
 171 * @package IXR
 172 * @since 1.5
 173 *
 174 */
 175class IXR_Message
 176{
 177    var $message;
 178    var $messageType;  // methodCall / methodResponse / fault
 179    var $faultCode;
 180    var $faultString;
 181    var $methodName;
 182    var $params;
 183
 184    // Current variable stacks
 185    var $_arraystructs = array();   // The stack used to keep track of the current array/struct
 186    var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array
 187    var $_currentStructName = array();  // A stack as well
 188    var $_param;
 189    var $_value;
 190    var $_currentTag;
 191    var $_currentTagContents;
 192    // The XML parser
 193    var $_parser;
 194
 195    function __construct($message)
 196    {
 197        $this->IXR_Message($message);
 198    }
 199
 200    function IXR_Message($message)
 201    {
 202        $this->message =& $message;
 203    }
 204
 205    function parse()
 206    {
 207        // first remove the XML declaration
 208        // merged from WP #10698 - this method avoids the RAM usage of preg_replace on very large messages
 209        $header = preg_replace( '/<\?xml.*?\?'.'>/', '', substr($this->message, 0, 100), 1);
 210        $this->message = substr_replace($this->message, $header, 0, 100);
 211        if (trim($this->message) == '') {
 212            return false;
 213        }
 214        $this->_parser = xml_parser_create();
 215        // Set XML parser to take the case of tags in to account
 216        xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
 217        // Set XML parser callback functions
 218        xml_set_object($this->_parser, $this);
 219        xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');
 220        xml_set_character_data_handler($this->_parser, 'cdata');
 221        $chunk_size = 262144; // 256Kb, parse in chunks to avoid the RAM usage on very large messages
 222        do {
 223            if (strlen($this->message) <= $chunk_size) {
 224                $final = true;
 225            }
 226            $part = substr($this->message, 0, $chunk_size);
 227            $this->message = substr($this->message, $chunk_size);
 228            if (!xml_parse($this->_parser, $part, $final)) {
 229                return false;
 230            }
 231            if ($final) {
 232                break;
 233            }
 234        } while (true);
 235        xml_parser_free($this->_parser);
 236
 237        // Grab the error messages, if any
 238        if ($this->messageType == 'fault') {
 239            $this->faultCode = $this->params[0]['faultCode'];
 240            $this->faultString = $this->params[0]['faultString'];
 241        }
 242        return true;
 243    }
 244
 245    function tag_open($parser, $tag, $attr)
 246    {
 247        $this->_currentTagContents = '';
 248        $this->currentTag = $tag;
 249        switch($tag) {
 250            case 'methodCall':
 251            case 'methodResponse':
 252            case 'fault':
 253                $this->messageType = $tag;
 254                break;
 255                /* Deal with stacks of arrays and structs */
 256            case 'data':    // data is to all intents and puposes more interesting than array
 257                $this->_arraystructstypes[] = 'array';
 258                $this->_arraystructs[] = array();
 259                break;
 260            case 'struct':
 261                $this->_arraystructstypes[] = 'struct';
 262                $this->_arraystructs[] = array();
 263                break;
 264        }
 265    }
 266
 267    function cdata($parser, $cdata)
 268    {
 269        $this->_currentTagContents .= $cdata;
 270    }
 271
 272    function tag_close($parser, $tag)
 273    {
 274        $valueFlag = false;
 275        switch($tag) {
 276            case 'int':
 277            case 'i4':
 278                $value = (int)trim($this->_currentTagContents);
 279                $valueFlag = true;
 280                break;
 281            case 'double':
 282                $value = (double)trim($this->_currentTagContents);
 283                $valueFlag = true;
 284                break;
 285            case 'string':
 286                $value = (string)trim($this->_currentTagContents);
 287                $valueFlag = true;
 288                break;
 289            case 'dateTime.iso8601':
 290                $value = new IXR_Date(trim($this->_currentTagContents));
 291                $valueFlag = true;
 292                break;
 293            case 'value':
 294                // "If no type is indicated, the type is string."
 295                if (trim($this->_currentTagContents) != '') {
 296                    $value = (string)$this->_currentTagContents;
 297                    $valueFlag = true;
 298                }
 299                break;
 300            case 'boolean':
 301                $value = (boolean)trim($this->_currentTagContents);
 302                $valueFlag = true;
 303                break;
 304            case 'base64':
 305                $value = base64_decode($this->_currentTagContents);
 306                $valueFlag = true;
 307                break;
 308                /* Deal with stacks of arrays and structs */
 309            case 'data':
 310            case 'struct':
 311                $value = array_pop($this->_arraystructs);
 312                array_pop($this->_arraystructstypes);
 313                $valueFlag = true;
 314                break;
 315            case 'member':
 316                array_pop($this->_currentStructName);
 317                break;
 318            case 'name':
 319                $this->_currentStructName[] = trim($this->_currentTagContents);
 320                break;
 321            case 'methodName':
 322                $this->methodName = trim($this->_currentTagContents);
 323                break;
 324        }
 325
 326        if ($valueFlag) {
 327            if (count($this->_arraystructs) > 0) {
 328                // Add value to struct or array
 329                if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {
 330                    // Add to struct
 331                    $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value;
 332                } else {
 333                    // Add to array
 334                    $this->_arraystructs[count($this->_arraystructs)-1][] = $value;
 335                }
 336            } else {
 337                // Just add as a paramater
 338                $this->params[] = $value;
 339            }
 340        }
 341        $this->_currentTagContents = '';
 342    }
 343}
 344
 345/**
 346 * IXR_Server
 347 *
 348 * @package IXR
 349 * @since 1.5
 350 */
 351class IXR_Server
 352{
 353    var $data;
 354    var $callbacks = array();
 355    var $message;
 356    var $capabilities;
 357
 358    function __construct($callbacks = false, $data = false, $wait = false)
 359    {
 360        $this->IXR_Server($callbacks, $data, $wait);
 361    }
 362
 363    function IXR_Server($callbacks = false, $data = false, $wait = false)
 364    {
 365        $this->setCapabilities();
 366        if ($callbacks) {
 367            $this->callbacks = $callbacks;
 368        }
 369        $this->setCallbacks();
 370        if (!$wait) {
 371            $this->serve($data);
 372        }
 373    }
 374
 375    function serve($data = false)
 376    {
 377        if (!$data) {
 378            if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] !== 'POST') {
 379            	header('Content-Type: text/plain'); // merged from WP #9093
 380                die('XML-RPC server accepts POST requests only.');
 381            }
 382
 383            //global $HTTP_RAW_POST_DATA;
 384            //if (empty($HTTP_RAW_POST_DATA)) {
 385                // workaround for a bug in PHP 5.2.2 - http://bugs.php.net/bug.php?id=41293
 386                $data = file_get_contents('php://input');
 387            //} else {
 388            //    $data =& $HTTP_RAW_POST_DATA;
 389            //}
 390        }
 391        $this->message = new IXR_Message($data);
 392        if (!$this->message->parse()) {
 393            $this->error(-32700, 'parse error. not well formed');
 394        }
 395        if ($this->message->messageType != 'methodCall') {
 396            $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall');
 397        }
 398        $result = $this->call($this->message->methodName, $this->message->params);
 399
 400        // Is the result an error?
 401        if (is_a($result, 'IXR_Error')) {
 402            $this->error($result);
 403        }
 404
 405        // Encode the result
 406        $r = new IXR_Value($result);
 407        $resultxml = $r->getXml();
 408
 409        // Create the XML
 410        $xml = <<<EOD
 411<methodResponse>
 412  <params>
 413    <param>
 414      <value>
 415      $resultxml
 416      </value>
 417    </param>
 418  </params>
 419</methodResponse>
 420
 421EOD;
 422      // Send it
 423      $this->output($xml);
 424    }
 425
 426    function call($methodname, $args)
 427    {
 428        if (!$this->hasMethod($methodname)) {
 429            return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.');
 430        }
 431        $method = $this->callbacks[$methodname];
 432
 433        // Perform the callback and send the response
 434        if (count($args) == 1) {
 435            // If only one paramater just send that instead of the whole array
 436            $args = $args[0];
 437        }
 438
 439        // Are we dealing with a function or a method?
 440        if (is_string($method) && substr($method, 0, 5) == 'this:') {
 441            // It's a class method - check it exists
 442            $method = substr($method, 5);
 443            if (!method_exists($this, $method)) {
 444                return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.');
 445            }
 446
 447            //Call the method
 448            $result = $this->$method($args);
 449        } else {
 450            // It's a function - does it exist?
 451            if (is_array($method)) {
 452                if (!method_exists($method[0], $method[1])) {
 453                    return new IXR_Error(-32601, 'server error. requested object method "'.$method[1].'" does not exist.');
 454                }
 455            } else if (!function_exists($method)) {
 456                return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.');
 457            }
 458
 459            // Call the function
 460            $result = call_user_func($method, $args);
 461        }
 462        return $result;
 463    }
 464
 465    function error($error, $message = false)
 466    {
 467        // Accepts either an error object or an error code and message
 468        if ($message && !is_object($error)) {
 469            $error = new IXR_Error($error, $message);
 470        }
 471        $this->output($error->getXml());
 472    }
 473
 474    function output($xml)
 475    {
 476        $xml = '<?xml version="1.0"?>'."\n".$xml;
 477        $length = strlen($xml);
 478        header('Connection: close');
 479        header('Content-Length: '.$length);
 480        header('Content-Type: text/xml');
 481        header('Date: '.date('r'));
 482        echo $xml;
 483        exit;
 484    }
 485
 486    function hasMethod($method)
 487    {
 488        return in_array($method, array_keys($this->callbacks));
 489    }
 490
 491    function setCapabilities()
 492    {
 493        // Initialises capabilities array
 494        $this->capabilities = array(
 495            'xmlrpc' => array(
 496                'specUrl' => 'http://www.xmlrpc.com/spec',
 497                'specVersion' => 1
 498        ),
 499            'faults_interop' => array(
 500                'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
 501                'specVersion' => 20010516
 502        ),
 503            'system.multicall' => array(
 504                'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
 505                'specVersion' => 1
 506        ),
 507        );
 508    }
 509
 510    function getCapabilities($args)
 511    {
 512        return $this->capabilities;
 513    }
 514
 515    function setCallbacks()
 516    {
 517        $this->callbacks['system.getCapabilities'] = 'this:getCapabilities';
 518        $this->callbacks['system.listMethods'] = 'this:listMethods';
 519        $this->callbacks['system.multicall'] = 'this:multiCall';
 520    }
 521
 522    function listMethods($args)
 523    {
 524        // Returns a list of methods - uses array_reverse to ensure user defined
 525        // methods are listed before server defined methods
 526        return array_reverse(array_keys($this->callbacks));
 527    }
 528
 529    function multiCall($methodcalls)
 530    {
 531        // See http://www.xmlrpc.com/discuss/msgReader$1208
 532        $return = array();
 533        foreach ($methodcalls as $call) {
 534            $method = $call['methodName'];
 535            $params = $call['params'];
 536            if ($method == 'system.multicall') {
 537                $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden');
 538            } else {
 539                $result = $this->call($method, $params);
 540            }
 541            if (is_a($result, 'IXR_Error')) {
 542                $return[] = array(
 543                    'faultCode' => $result->code,
 544                    'faultString' => $result->message
 545                );
 546            } else {
 547                $return[] = array($result);
 548            }
 549        }
 550        return $return;
 551    }
 552}
 553
 554/**
 555 * IXR_Request
 556 *
 557 * @package IXR
 558 * @since 1.5
 559 */
 560class IXR_Request
 561{
 562    var $method;
 563    var $args;
 564    var $xml;
 565
 566    function __construct($method, $args)
 567    {
 568        $this->IXR_Request($method, $args);
 569    }
 570
 571    function IXR_Request($method, $args)
 572    {
 573        $this->method = $method;
 574        $this->args = $args;
 575        $this->xml = <<<EOD
 576<?xml version="1.0"?>
 577<methodCall>
 578<methodName>{$this->method}</methodName>
 579<params>
 580
 581EOD;
 582        foreach ($this->args as $arg) {
 583            $this->xml .= '<param><value>';
 584            $v = new IXR_Value($arg);
 585            $this->xml .= $v->getXml();
 586            $this->xml .= "</value></param>\n";
 587        }
 588        $this->xml .= '</params></methodCall>';
 589    }
 590
 591    function getLength()
 592    {
 593        return strlen($this->xml);
 594    }
 595
 596    function getXml()
 597    {
 598        return $this->xml;
 599    }
 600}
 601
 602/**
 603 * IXR_Client
 604 *
 605 * @package IXR
 606 * @since 1.5
 607 *
 608 */
 609class IXR_Client
 610{
 611    var $server;
 612    var $port;
 613    var $path;
 614    var $useragent;
 615    var $response;
 616    var $message = false;
 617    var $debug = false;
 618    var $timeout;
 619
 620    // Storage place for an error message
 621    var $error = false;
 622
 623    function __construct($server, $path = false, $port = 80, $timeout = 15)
 624    {
 625        $this->IXR_Client($server, $path, $port, $timeout);
 626    }
 627
 628    function IXR_Client($server, $path = false, $port = 80, $timeout = 15)
 629    {
 630        if (!$path) {
 631            // Assume we have been given a URL instead
 632            $bits = parse_url($server);
 633            $this->server = $bits['host'];
 634            $this->port = isset($bits['port']) ? $bits['port'] : 80;
 635            $this->path = isset($bits['path']) ? $bits['path'] : '/';
 636
 637            // Make absolutely sure we have a path
 638            if (!$this->path) {
 639                $this->path = '/';
 640            }
 641        } else {
 642            $this->server = $server;
 643            $this->path = $path;
 644            $this->port = $port;
 645        }
 646        $this->useragent = 'The Incutio XML-RPC PHP Library';
 647        $this->timeout = $timeout;
 648    }
 649
 650    function query()
 651    {
 652        $args = func_get_args();
 653        $method = array_shift($args);
 654        $request = new IXR_Request($method, $args);
 655        $length = $request->getLength();
 656        $xml = $request->getXml();
 657        $r = "\r\n";
 658        $request  = "POST {$this->path} HTTP/1.0$r";
 659
 660        // Merged from WP #8145 - allow custom headers
 661        $this->headers['Host']          = $this->server;
 662        $this->headers['Content-Type']  = 'text/xml';
 663        $this->headers['User-Agent']    = $this->useragent;
 664        $this->headers['Content-Length']= $length;
 665
 666        foreach( $this->headers as $header => $value ) {
 667            $request .= "{$header}: {$value}{$r}";
 668        }
 669        $request .= $r;
 670
 671        $request .= $xml;
 672
 673        // Now send the request
 674        if ($this->debug) {
 675            echo '<pre class="ixr_request">'.htmlspecialchars($request)."\n</pre>\n\n";
 676        }
 677
 678        if ($this->timeout) {
 679            $fp = @fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout);
 680        } else {
 681            $fp = @fsockopen($this->server, $this->port, $errno, $errstr);
 682        }
 683        if (!$fp) {
 684            $this->error = new IXR_Error(-32300, 'transport error - could not open socket');
 685            return false;
 686        }
 687        fputs($fp, $request);
 688        $contents = '';
 689        $debugContents = '';
 690        $gotFirstLine = false;
 691        $gettingHeaders = true;
 692        while (!feof($fp)) {
 693            $line = fgets($fp, 4096);
 694            if (!$gotFirstLine) {
 695                // Check line for '200'
 696                if (strstr($line, '200') === false) {
 697                    $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200');
 698                    return false;
 699                }
 700                $gotFirstLine = true;
 701            }
 702            if (trim($line) == '') {
 703                $gettingHeaders = false;
 704            }
 705            if (!$gettingHeaders) {
 706            	// merged from WP #12559 - remove trim
 707                $contents .= $line;
 708            }
 709            if ($this->debug) {
 710            	$debugContents .= $line;
 711            }
 712        }
 713        if ($this->debug) {
 714            echo '<pre class="ixr_response">'.htmlspecialchars($debugContents)."\n</pre>\n\n";
 715        }
 716
 717        // Now parse what we've got back
 718        $this->message = new IXR_Message($contents);
 719        if (!$this->message->parse()) {
 720            // XML error
 721            $this->error = new IXR_Error(-32700, 'parse error. not well formed');
 722            return false;
 723        }
 724
 725        // Is the message a fault?
 726        if ($this->message->messageType == 'fault') {
 727            $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);
 728            return false;
 729        }
 730
 731        // Message must be OK
 732        return true;
 733    }
 734
 735    function getResponse()
 736    {
 737        // methodResponses can only have one param - return that
 738        return $this->message->params[0];
 739    }
 740
 741    function isError()
 742    {
 743        return (is_object($this->error));
 744    }
 745
 746    function getErrorCode()
 747    {
 748        return $this->error->code;
 749    }
 750
 751    function getErrorMessage()
 752    {
 753        return $this->error->message;
 754    }
 755}
 756
 757
 758/**
 759 * IXR_Error
 760 *
 761 * @package IXR
 762 * @since 1.5
 763 */
 764class IXR_Error
 765{
 766    var $code;
 767    var $message;
 768
 769    function __construct($code, $message)
 770    {
 771        $this->IXR_Error($code, $message);
 772    }
 773
 774    function IXR_Error($code, $message)
 775    {
 776        $this->code = $code;
 777        $this->message = htmlspecialchars($message);
 778    }
 779
 780    function getXml()
 781    {
 782        $xml = <<<EOD
 783<methodResponse>
 784  <fault>
 785    <value>
 786      <struct>
 787        <member>
 788          <name>faultCode</name>
 789          <value><int>{$this->code}</int></value>
 790        </member>
 791        <member>
 792          <name>faultString</name>
 793          <value><string>{$this->message}</string></value>
 794        </member>
 795      </struct>
 796    </value>
 797  </fault>
 798</methodResponse>
 799
 800EOD;
 801        return $xml;
 802    }
 803}
 804
 805/**
 806 * IXR_Date
 807 *
 808 * @package IXR
 809 * @since 1.5
 810 */
 811class IXR_Date {
 812    var $year;
 813    var $month;
 814    var $day;
 815    var $hour;
 816    var $minute;
 817    var $second;
 818    var $timezone;
 819
 820    function __construct($time)
 821    {
 822        $this->IXR_Date($time);
 823    }
 824
 825    function IXR_Date($time)
 826    {
 827        // $time can be a PHP timestamp or an ISO one
 828        if (is_numeric($time)) {
 829            $this->parseTimestamp($time);
 830        } else {
 831            $this->parseIso($time);
 832        }
 833    }
 834
 835    function parseTimestamp($timestamp)
 836    {
 837        $this->year = date('Y', $timestamp);
 838        $this->month = date('m', $timestamp);
 839        $this->day = date('d', $timestamp);
 840        $this->hour = date('H', $timestamp);
 841        $this->minute = date('i', $timestamp);
 842        $this->second = date('s', $timestamp);
 843        $this->timezone = '';
 844    }
 845
 846    function parseIso($iso)
 847    {
 848        $this->year = substr($iso, 0, 4);
 849        $this->month = substr($iso, 4, 2);
 850        $this->day = substr($iso, 6, 2);
 851        $this->hour = substr($iso, 9, 2);
 852        $this->minute = substr($iso, 12, 2);
 853        $this->second = substr($iso, 15, 2);
 854        $this->timezone = substr($iso, 17);
 855    }
 856
 857    function getIso()
 858    {
 859        return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second.$this->timezone;
 860    }
 861
 862    function getXml()
 863    {
 864        return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>';
 865    }
 866
 867    function getTimestamp()
 868    {
 869        return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);
 870    }
 871}
 872
 873/**
 874 * IXR_Base64
 875 *
 876 * @package IXR
 877 * @since 1.5
 878 */
 879class IXR_Base64
 880{
 881    var $data;
 882
 883    function __construct($data)
 884    {
 885        $this->IXR_Base64($data);
 886    }
 887
 888    function IXR_Base64($data)
 889    {
 890        $this->data = $data;
 891    }
 892
 893    function getXml()
 894    {
 895        return '<base64>'.base64_encode($this->data).'</base64>';
 896    }
 897}
 898
 899/**
 900 * IXR_IntrospectionServer
 901 *
 902 * @package IXR
 903 * @since 1.5
 904 */
 905class IXR_IntrospectionServer extends IXR_Server
 906{
 907    var $signatures;
 908    var $help;
 909
 910    function __construct()
 911    {
 912        $this->IXR_IntrospectionServer();
 913    }
 914
 915    function IXR_IntrospectionServer()
 916    {
 917        $this->setCallbacks();
 918        $this->setCapabilities();
 919        $this->capabilities['introspection'] = array(
 920            'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html',
 921            'specVersion' => 1
 922        );
 923        $this->addCallback(
 924            'system.methodSignature',
 925            'this:methodSignature',
 926            array('array', 'string'),
 927            'Returns an array describing the return type and required parameters of a method'
 928        );
 929        $this->addCallback(
 930            'system.getCapabilities',
 931            'this:getCapabilities',
 932            array('struct'),
 933            'Returns a struct describing the XML-RPC specifications supported by this server'
 934        );
 935        $this->addCallback(
 936            'system.listMethods',
 937            'this:listMethods',
 938            array('array'),
 939            'Returns an array of available methods on this server'
 940        );
 941        $this->addCallback(
 942            'system.methodHelp',
 943            'this:methodHelp',
 944            array('string', 'string'),
 945            'Returns a documentation string for the specified method'
 946        );
 947    }
 948
 949    function addCallback($method, $callback, $args, $help)
 950    {
 951        $this->callbacks[$method] = $callback;
 952        $this->signatures[$method] = $args;
 953        $this->help[$method] = $help;
 954    }
 955
 956    function call($methodname, $args)
 957    {
 958        // Make sure it's in an array
 959        if ($args && !is_array($args)) {
 960            $args = array($args);
 961        }
 962
 963        // Over-rides default call method, adds signature check
 964        if (!$this->hasMethod($methodname)) {
 965            return new IXR_Error(-32601, 'server error. requested method "'.$this->message->methodName.'" not specified.');
 966        }
 967        $method = $this->callbacks[$methodname];
 968        $signature = $this->signatures[$methodname];
 969        $returnType = array_shift($signature);
 970
 971        // Check the number of arguments
 972        if (count($args) != count($signature)) {
 973            return new IXR_Error(-32602, 'server error. wrong number of method parameters');
 974        }
 975
 976        // Check the argument types
 977        $ok = true;
 978        $argsbackup = $args;
 979        for ($i = 0, $j = count($args); $i < $j; $i++) {
 980            $arg = array_shift($args);
 981            $type = array_shift($signature);
 982            switch ($type) {
 983                case 'int':
 984                case 'i4':
 985                    if (is_array($arg) || !is_int($arg)) {
 986                        $ok = false;
 987                    }
 988                    break;
 989                case 'base64':
 990                case 'string':
 991                    if (!is_string($arg)) {
 992                        $ok = false;
 993                    }
 994                    break;
 995                case 'boolean':
 996                    if ($arg !== false && $arg !== true) {
 997                        $ok = false;
 998                    }
 999                    break;
1000                case 'float':
1001                case 'double':
1002                    if (!is_float($arg)) {
1003                        $ok = false;
1004                    }
1005                    break;
1006                case 'date':
1007                case 'dateTime.iso8601':
1008                    if (!is_a($arg, 'IXR_Date')) {
1009                        $ok = false;
1010                    }
1011                    break;
1012            }
1013            if (!$ok) {
1014                return new IXR_Error(-32602, 'server error. invalid method parameters');
1015            }
1016        }
1017        // It passed the test - run the "real" method call
1018        return parent::call($methodname, $argsbackup);
1019    }
1020
1021    function methodSignature($method)
1022    {
1023        if (!$this->hasMethod($method)) {
1024            return new IXR_Error(-32601, 'server error. requested method "'.$method.'" not specified.');
1025        }
1026        // We should be returning an array of types
1027        $types = $this->signatures[$method];
1028        $return = array();
1029        foreach ($types as $type) {
1030            switch ($type) {
1031                case 'string':
1032                    $return[] = 'string';
1033                    break;
1034                case 'int':
1035                case 'i4':
1036                    $return[] = 42;
1037                    break;
1038                case 'double':
1039                    $return[] = 3.1415;
1040                    break;
1041                case 'dateTime.iso8601':
1042                    $return[] = new IXR_Date(time());
1043                    break;
1044                case 'boolean':
1045                    $return[] = true;
1046                    break;
1047                case 'base64':
1048                    $return[] = new IXR_Base64('base64');
1049                    break;
1050                case 'array':
1051                    $return[] = array('array');
1052                    break;
1053                case 'struct':
1054                    $return[] = array('struct' => 'struct');
1055                    break;
1056            }
1057        }
1058        return $return;
1059    }
1060
1061    function methodHelp($method)
1062    {
1063        return $this->help[$method];
1064    }
1065}
1066
1067/**
1068 * IXR_ClientMulticall
1069 *
1070 * @package IXR
1071 * @since 1.5
1072 */
1073class IXR_ClientMulticall extends IXR_Client
1074{
1075    var $calls = array();
1076
1077    function __construct($server, $path = false, $port = 80)
1078    {
1079        $this->IXR_ClientMulticall($server, $path, $port);
1080    }
1081
1082    function IXR_ClientMulticall($server, $path = false, $port = 80)
1083    {
1084        parent::IXR_Client($server, $path, $port);
1085        $this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)';
1086    }
1087
1088    function addCall()
1089    {
1090        $args = func_get_args();
1091        $methodName = array_shift($args);
1092        $struct = array(
1093            'methodName' => $methodName,
1094            'params' => $args
1095        );
1096        $this->calls[] = $struct;
1097    }
1098
1099    function query()
1100    {
1101        // Prepare multicall, then call the parent::query() method
1102        return parent::query('system.multicall', $this->calls);
1103    }
1104}
1105
1106/**
1107 * Client for communicating with a XML-RPC Server over HTTPS.
1108 *
1109 * @author Jason Stirk <jstirk@gmm.com.au> (@link http://blog.griffin.homelinux.org/projects/xmlrpc/)
1110 * @version 0.2.0 26May2005 08:34 +0800
1111 * @copyright (c) 2004-2005 Jason Stirk
1112 * @package IXR
1113 */
1114class IXR_ClientSSL extends IXR_Client
1115{
1116    /**
1117     * Filename of the SSL Client Certificate
1118     * @access private
1119     * @since 0.1.0
1120     * @var string
1121     */
1122    var $_certFile;
1123
1124    /**
1125     * Filename of the SSL CA Certificate
1126     * @access private
1127     * @since 0.1.0
1128     * @var string
1129     */
1130    var $_caFile;
1131
1132    /**
1133     * Filename of the SSL Client Private Key
1134     * @access private
1135     * @since 0.1.0
1136     * @var string
1137     */
1138    var $_keyFile;
1139
1140    /**
1141     * Passphrase to unlock the private key
1142     * @access private
1143     * @since 0.1.0
1144     * @var string
1145     */
1146    var $_passphrase;
1147
1148    function __construct($server, $path = false, $port = 443, $timeout = false)
1149    {
1150        $this->IXR_ClientSSL($server, $path, $port, $timeout);
1151    }
1152
1153    /**
1154     * Constructor
1155     * @param string $server URL of the Server to connect to
1156     * @since 0.1.0
1157     */
1158    function IXR_ClientSSL($server, $path = false, $port = 443, $timeout = false)
1159    {
1160        parent::IXR_Client($server, $path, $port, $timeout);
1161        $this->useragent = 'The Incutio XML-RPC PHP Library for SSL';
1162
1163        // Set class fields
1164        $this->_certFile=false;
1165        $this->_caFile=false;
1166        $this->_keyFile=false;
1167        $this->_passphrase='';
1168    }
1169
1170    /**
1171     * Set the client side certificates to communicate with the server.
1172     *
1173     * @since 0.1.0
1174     * @param string $certificateFile Filename of the client side certificate to use
1175     * @param string $keyFile Filename of the client side certificate's private key
1176     * @param string $keyPhrase Passphrase to unlock the private key
1177     */
1178    function setCertificate($certificateFile, $keyFile, $keyPhrase='')
1179    {
1180        // Check the files all exist
1181        if (is_file($certificateFile)) {
1182            $this->_certFile = $certificateFile;
1183        } else {
1184            die('Could not open certificate: ' . $certificateFile);
1185        }
1186
1187        if (is_file($keyFile)) {
1188            $this->_keyFile = $keyFile;
1189        } else {
1190            die('Could not open private key: ' . $keyFile);
1191        }
1192
1193        $this->_passphrase=(string)$keyPhrase;
1194    }
1195
1196    function setCACertificate($caFile)
1197    {
1198        if (is_file($caFile)) {
1199            $this->_caFile = $caFile;
1200        } else {
1201            die('Could not open CA certificate: ' . $caFile);
1202        }
1203    }
1204
1205    /**
1206     * Sets the connection timeout (in seconds)
1207     * @param int $newTimeOut Timeout in seconds
1208     * @returns void
1209     * @since 0.1.2
1210     */
1211    function setTimeOut($newTimeOut)
1212    {
1213        $this->timeout = (int)$newTimeOut;
1214    }
1215
1216    /**
1217     * Returns the connection timeout (in seconds)
1218     * @returns int
1219     * @since 0.1.2
1220     */
1221    function getTimeOut()
1222    {
1223        return $this->timeout;
1224    }
1225
1226    /**
1227     * Set the query to send to the XML-RPC Server
1228     * @since 0.1.0
1229     */
1230    function query()
1231    {
1232        $args = func_get_args();
1233        $method = array_shift($args);
1234        $request = new IXR_Request($method, $args);
1235        $length = $request->getLength();
1236        $xml = $request->getXml();
1237
1238        if ($this->debug) {
1239            echo '<pre>'.htmlspecialchars($xml)."\n</pre>\n\n";
1240        }
1241
1242        //This is where we deviate from the normal query()
1243        //Rather than open a normal sock, we will actually use the cURL
1244        //extensions to make the calls, and handle the SSL stuff.
1245
1246        //Since 04Aug2004 (0.1.3) - Need to include the port (duh...)
1247        //Since 06Oct2004 (0.1.4) - Need to include the colon!!!
1248        //        (I swear I've fixed this before... ESP in live... But anyhu...)
1249        $curl=curl_init('https://' . $this->server . ':' . $this->port . $this->path);
1250        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
1251
1252        //Since 23Jun2004 (0.1.2) - Made timeout a class field
1253        curl_setopt($curl, CURLOPT_TIMEOUT, $this->timeout);
1254
1255        if ($this->debug) {
1256            curl_setopt($curl, CURLOPT_VERBOSE, 1);
1257        }
1258
1259        curl_setopt($curl, CURLOPT_HEADER, 1);
1260        curl_setopt($curl, CURLOPT_POST, 1);
1261        curl_setopt($curl, CURLOPT_POSTFIELDS, $xml);
1262        curl_setopt($curl, CURLOPT_PORT, $this->port);
1263        curl_setopt($curl, CURLOPT_HTTPHEADER, array(
1264                                    "Content-Type: text/xml",
1265                                    "Content-length: {$length}"));
1266
1267        // Process the SSL certificates, etc. to use
1268        if (!($this->_certFile === false)) {
1269            // We have a certificate file set, so add these to the cURL handler
1270            curl_setopt($curl, CURLOPT_SSLCERT, $this->_certFile);
1271            curl_setopt($curl, CURLOPT_SSLKEY, $this->_keyFile);
1272
1273            if ($this->debug) {
1274                echo "SSL Cert at : " . $this->_certFile . "\n";
1275                echo "SSL Key at : " . $this->_keyFile . "\n";
1276            }
1277
1278            // See if we need to give a passphrase
1279            if (!($this->_passphrase === '')) {
1280                curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $this->_passphrase);
1281            }
1282
1283            if ($this->_caFile === false) {
1284                // Don't verify their certificate, as we don't have a CA to verify against
1285                curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
1286            } else {
1287                // Verify against a CA
1288                curl_setopt($curl, CURLOPT_CAINFO, $this->_caFile);
1289            }
1290        }
1291
1292        // Call cURL to do it's stuff and return us the content
1293        $contents = curl_exec($curl);
1294        curl_close($curl);
1295
1296        // Check for 200 Code in $contents
1297        if (!strstr($contents, '200 OK')) {
1298            //There was no "200 OK" returned - we failed
1299            $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200');
1300            return false;
1301        }
1302
1303        if ($this->debug) {
1304            echo '<pre>'.htmlspecialchars($contents)."\n</pre>\n\n";
1305        }
1306        // Now parse what we've got back
1307        // Since 20Jun2004 (0.1.1) - We need to remove the headers first
1308        // Why I have only just found this, I will never know...
1309        // So, remove everything before the first <
1310        $contents = substr($contents,strpos($contents, '<'));
1311
1312        $this->message = new IXR_Message($contents);
1313        if (!$this->message->parse()) {
1314            // XML error
1315            $this->error = new IXR_Error(-32700, 'parse error. not well formed');
1316            return false;
1317        }
1318        // Is the message a fault?
1319        if ($this->message->messageType == 'fault') {
1320            $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);
1321            return false;
1322        }
1323
1324        // Message must be OK
1325        return true;
1326    }
1327}
1328
1329/**
1330 * Extension of the {@link IXR_Server} class to easily wrap objects.
1331 *
1332 * Class is designed to extend the existing XML-RPC server to allow the
1333 * presentation of methods from a variety of different objects via an
1334 * XML-RPC server.
1335 * It is intended to assist in organization of your XML-RPC methods by allowing
1336 * you to "write once" in your existing model classes and present them.
1337 *
1338 * @author Jason Stirk <jstirk@gmm.com.au>
1339 * @version 1.0.1 19Apr2005 17:40 +0800
1340 * @copyright Copyright (c) 2005 Jason Stirk
1341 * @package IXR
1342 */
1343class IXR_ClassServer extends IXR_Server
1344{
1345    var $_objects;
1346    var $_delim;
1347
1348    function __construct($delim = '.', $wait = false)
1349    {
1350        $this->IXR_ClassServer($delim, $wait);
1351    }
1352
1353    function IXR_ClassServer($delim = '.', $wait = false)
1354    {
1355        $this->IXR_Server(array(), false, $wait);
1356        $this->_delimiter = $delim;
1357        $this->_objects = array();
1358    }
1359
1360    function addMethod($rpcName, $functionName)
1361    {
1362        $this->callbacks[$rpcName] = $functionName;
1363    }
1364
1365    function registerObject($object, $methods, $prefix=null)
1366    {
1367        if (is_null($prefix))
1368        {
1369            $prefix = get_class($object);
1370        }
1371        $this->_objects[$prefix] = $object;
1372
1373        // Add to our callbacks array
1374        foreach($methods as $method)
1375        {
1376            if (is_array($method))
1377            {
1378                $targetMethod = $method[0];
1379                $method = $method[1];
1380            }
1381            else
1382            {
1383                $targetMethod = $method;
1384            }
1385            $this->callbacks[$prefix . $this->_delimiter . $method]=array($prefix, $targetMethod);
1386        }
1387    }
1388
1389    function call($methodname, $args)
1390    {
1391        if (!$this->hasMethod($methodname)) {
1392            return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.');
1393        }
1394        $method = $this->callbacks[$methodname];
1395
1396        // Perform the callback and send the response
1397        if (count($args) == 1) {
1398            // If only one paramater just send that instead of the whole array
1399            $args = $args[0];
1400        }
1401
1402        // See if this method comes from one of our objects or maybe self
1403        if (is_array($method) || (substr($method, 0, 5) == 'this:')) {
1404            if (is_array($method)) {
1405                $object=$this->_objects[$method[0]];
1406                $method=$method[1];
1407            } else {
1408                $object=$this;
1409                $method = substr($method, 5);
1410            }
1411
1412            // It's a class method - check it exists
1413            if (!method_exists($object, $method)) {
1414                return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.');
1415            }
1416
1417            // Call the method
1418            $result = $object->$method($args);
1419        } else {
1420            // It's a function - does it exist?
1421            if (!function_exists($method)) {
1422                return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.');
1423            }
1424
1425            // Call the function
1426            $result = $method($args);
1427        }
1428        return $result;
1429    }
1430}
1431?>