Textpattern PHP Cross Reference Content Management Systems

Source: /textpattern/lib/IXRClass.php - 1432 lines - 43892 bytes - Summary - Text - Print

Description: IXR - The Incutio XML-RPC Library Copyright (c) 2010, Incutio Ltd. All rights reserved.

   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 (Contains Textpatternish amendments, 2014-08-13)
  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 __construct($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 __construct($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 __construct($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 - https://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          $xml = '<?xml version="1.0" encoding="utf-8"?>'."\n".$xml;
 496          if ( (@strpos($_SERVER["HTTP_ACCEPT_ENCODING"],'gzip') !== false) && extension_loaded('zlib') &&
 497              ini_get("zlib.output_compression") == 0 && ini_get('output_handler') != 'ob_gzhandler' && !headers_sent())
 498          {
 499              $xml = gzencode($xml,7,FORCE_GZIP);
 500              header("Content-Encoding: gzip");
 501          }
 502          $length = strlen($xml);
 503          header('Connection: close');
 504          header('Content-Length: '.$length);
 505          header('Content-Type: text/xml');
 506          header('Date: '.date('r'));
 507          echo $xml;
 508          exit;
 509      }
 510  
 511      function hasMethod($method)
 512      {
 513          return in_array($method, array_keys($this->callbacks));
 514      }
 515  
 516      function setCapabilities()
 517      {
 518          // Initialises capabilities array
 519          $this->capabilities = array(
 520              'xmlrpc' => array(
 521                  'specUrl' => 'http://www.xmlrpc.com/spec',
 522                  'specVersion' => 1
 523          ),
 524              'faults_interop' => array(
 525                  'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
 526                  'specVersion' => 20010516
 527          ),
 528              'system.multicall' => array(
 529                  'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
 530                  'specVersion' => 1
 531          ),
 532          );
 533      }
 534  
 535      function getCapabilities($args)
 536      {
 537          return $this->capabilities;
 538      }
 539  
 540      function setCallbacks()
 541      {
 542          $this->callbacks['system.getCapabilities'] = 'this:getCapabilities';
 543          $this->callbacks['system.listMethods'] = 'this:listMethods';
 544          $this->callbacks['system.multicall'] = 'this:multiCall';
 545      }
 546  
 547      function listMethods($args)
 548      {
 549          // Returns a list of methods - uses array_reverse to ensure user defined
 550          // methods are listed before server defined methods
 551          return array_reverse(array_keys($this->callbacks));
 552      }
 553  
 554      function multiCall($methodcalls)
 555      {
 556          // See http://www.xmlrpc.com/discuss/msgReader$1208
 557          $return = array();
 558          foreach ($methodcalls as $call) {
 559              $method = $call['methodName'];
 560              $params = $call['params'];
 561              if ($method == 'system.multicall') {
 562                  $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden');
 563              } else {
 564                  $result = $this->call($method, $params);
 565              }
 566              if (is_a($result, 'IXR_Error')) {
 567                  $return[] = array(
 568                      'faultCode' => $result->code,
 569                      'faultString' => $result->message
 570                  );
 571              } else {
 572                  $return[] = array($result);
 573              }
 574          }
 575          return $return;
 576      }
 577  }
 578  
 579  /**
 580   * IXR_Request
 581   *
 582   * @package IXR
 583   * @since 1.5.0
 584   */
 585  class IXR_Request
 586  {
 587      var $method;
 588      var $args;
 589      var $xml;
 590  
 591      function __construct($method, $args)
 592      {
 593          $this->method = $method;
 594          $this->args = $args;
 595          $this->xml = <<<EOD
 596  <?xml version="1.0"?>
 597  <methodCall>
 598  <methodName>{$this->method}</methodName>
 599  <params>
 600  
 601  EOD;
 602          foreach ($this->args as $arg) {
 603              $this->xml .= '<param><value>';
 604              $v = new IXR_Value($arg);
 605              $this->xml .= $v->getXml();
 606              $this->xml .= "</value></param>\n";
 607          }
 608          $this->xml .= '</params></methodCall>';
 609      }
 610  
 611      function getLength()
 612      {
 613          return strlen($this->xml);
 614      }
 615  
 616      function getXml()
 617      {
 618          return $this->xml;
 619      }
 620  }
 621  
 622  /**
 623   * IXR_Client
 624   *
 625   * @package IXR
 626   * @since 1.5.0
 627   *
 628   */
 629  class IXR_Client
 630  {
 631      var $server;
 632      var $port;
 633      var $path;
 634      var $useragent;
 635      var $response;
 636      var $message = false;
 637      var $debug = false;
 638      var $timeout;
 639      var $headers = array();
 640  
 641      // Storage place for an error message
 642      var $error = false;
 643  
 644      function __construct($server, $path = false, $port = 80, $timeout = 45)
 645      {
 646          if (!$path) {
 647              // Assume we have been given a URL instead
 648              $bits = parse_url($server);
 649              $this->server = $bits['host'];
 650              $this->port = isset($bits['port']) ? $bits['port'] : 80;
 651              $this->path = isset($bits['path']) ? $bits['path'] : '/';
 652  
 653              // Make absolutely sure we have a path
 654              if (!$this->path) {
 655                  $this->path = '/';
 656              }
 657  
 658              if ( ! empty( $bits['query'] ) ) {
 659                  $this->path .= '?' . $bits['query'];
 660              }
 661          } else {
 662              $this->server = $server;
 663              $this->path = $path;
 664              $this->port = $port;
 665          }
 666          $this->useragent = 'The Incutio XML-RPC PHP Library';
 667          $this->timeout = $timeout;
 668      }
 669  
 670      function query()
 671      {
 672          $args = func_get_args();
 673          $method = array_shift($args);
 674          $request = new IXR_Request($method, $args);
 675          $length = $request->getLength();
 676          $xml = $request->getXml();
 677          $r = "\r\n";
 678          $request  = "POST {$this->path} HTTP/1.0$r";
 679  
 680          // Merged from WP #8145 - allow custom headers
 681          $this->headers['Host']          = $this->server;
 682          $this->headers['Content-Type']  = 'text/xml';
 683          $this->headers['User-Agent']    = $this->useragent;
 684          $this->headers['Content-Length']= $length;
 685  
 686          // Accept gzipped response if zlib and if php4.3+ (fgets turned binary safe)
 687          if ( extension_loaded('zlib') && preg_match('#^(4\.[3-9])|([5-9])#',phpversion()) )
 688              $this->headers['Accept-Encoding']    = 'gzip';
 689  
 690          foreach( $this->headers as $header => $value ) {
 691              $request .= "{$header}: {$value}{$r}";
 692          }
 693          $request .= $r;
 694  
 695          $request .= $xml;
 696  
 697          // Now send the request
 698          if ($this->debug) {
 699              echo '<pre class="ixr_request">'.htmlspecialchars($request)."\n</pre>\n\n";
 700          }
 701  
 702          if ($this->timeout) {
 703              $fp = (!is_disabled('fsockopen')) ? fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout) : false;
 704          } else {
 705              $fp = (!is_disabled('fsockopen')) ? fsockopen($this->server, $this->port, $errno, $errstr) : false;
 706          }
 707          if (!$fp) {
 708              $this->error = new IXR_Error(-32300, 'transport error - could not open socket ('.$errstr.')');
 709              return false;
 710          }
 711          fputs($fp, $request);
 712          $contents = '';
 713          $debugContents = '';
 714          $gotFirstLine = false;
 715          $gettingHeaders = true;
 716          $is_gzipped = false;
 717          while (!feof($fp)) {
 718              $line = fgets($fp, 4096);
 719              if (!$gotFirstLine) {
 720                  // Check line for '200'
 721                  if (strstr($line, '200') === false) {
 722                      $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200');
 723                      return false;
 724                  }
 725                  $gotFirstLine = true;
 726              }
 727              if ($gettingHeaders && trim($line) == '') {
 728                  $gettingHeaders = false;
 729                  continue;
 730              }
 731              if (!$gettingHeaders) {
 732                  // We do a binary comparison of the first two bytes, see
 733                  // rfc1952, to check wether the content is gzipped.
 734                  if ( ($contents=='') && (strncmp($line,"\x1F\x8B",2)===0))
 735                      $is_gzipped = true;
 736                  // merged from WP #12559 - remove trim
 737                  $contents .= $line;
 738              }
 739              if ($this->debug) {
 740                  $debugContents .= $line;
 741              }
 742          }
 743          // if gzipped, strip the 10 byte header, and pass it to gzinflate (rfc1952)
 744          if ($is_gzipped)
 745          {
 746              $contents = gzinflate(substr($contents, 10));
 747              //simulate trim() for each line; don't know why, but it won't work otherwise
 748              $contents = preg_replace('#^[\x20\x09\x0A\x0D\x00\x0B]*(.*)[\x20\x09\x0A\x0D\x00\x0B]*$#m','\\1',$contents);
 749          }
 750          if ($this->debug) {
 751              echo '<pre class="ixr_response">'.htmlspecialchars($debugContents)."\n</pre>\n\n";
 752          }
 753  
 754          // Now parse what we've got back
 755          $this->message = new IXR_Message($contents);
 756          if (!$this->message->parse()) {
 757              // XML error
 758              $this->error = new IXR_Error(-32700, 'parse error. not well formed');
 759              return false;
 760          }
 761  
 762          // Is the message a fault?
 763          if ($this->message->messageType == 'fault') {
 764              $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);
 765              return false;
 766          }
 767  
 768          // Message must be OK
 769          return true;
 770      }
 771  
 772      function getResponse()
 773      {
 774          // methodResponses can only have one param - return that
 775          return $this->message->params[0];
 776      }
 777  
 778      function isError()
 779      {
 780          return (is_object($this->error));
 781      }
 782  
 783      function getErrorCode()
 784      {
 785          return $this->error->code;
 786      }
 787  
 788      function getErrorMessage()
 789      {
 790          return $this->error->message;
 791      }
 792  }
 793  
 794  
 795  /**
 796   * IXR_Error
 797   *
 798   * @package IXR
 799   * @since 1.5.0
 800   */
 801  class IXR_Error
 802  {
 803      var $code;
 804      var $message;
 805  
 806      function __construct($code, $message)
 807      {
 808          $this->code = $code;
 809          $this->message = htmlspecialchars($message);
 810      }
 811  
 812      function getXml()
 813      {
 814          $xml = <<<EOD
 815  <methodResponse>
 816    <fault>
 817      <value>
 818        <struct>
 819          <member>
 820            <name>faultCode</name>
 821            <value><int>{$this->code}</int></value>
 822          </member>
 823          <member>
 824            <name>faultString</name>
 825            <value><string>{$this->message}</string></value>
 826          </member>
 827        </struct>
 828      </value>
 829    </fault>
 830  </methodResponse>
 831  
 832  EOD;
 833          return $xml;
 834      }
 835  }
 836  
 837  /**
 838   * IXR_Date
 839   *
 840   * @package IXR
 841   * @since 1.5.0
 842   */
 843  class IXR_Date {
 844      var $year;
 845      var $month;
 846      var $day;
 847      var $hour;
 848      var $minute;
 849      var $second;
 850      var $timezone;
 851  
 852      function __construct($time)
 853      {
 854          // $time can be a PHP timestamp or an ISO one
 855          if (is_numeric($time)) {
 856              $this->parseTimestamp($time);
 857          } else {
 858              $this->parseIso($time);
 859          }
 860      }
 861  
 862      function parseTimestamp($timestamp)
 863      {
 864          $this->year = date('Y', $timestamp);
 865          $this->month = date('m', $timestamp);
 866          $this->day = date('d', $timestamp);
 867          $this->hour = date('H', $timestamp);
 868          $this->minute = date('i', $timestamp);
 869          $this->second = date('s', $timestamp);
 870          $this->timezone = '';
 871      }
 872  
 873      function parseIso($iso)
 874      {
 875          $this->year = substr($iso, 0, 4);
 876          $this->month = substr($iso, 4, 2);
 877          $this->day = substr($iso, 6, 2);
 878          $this->hour = substr($iso, 9, 2);
 879          $this->minute = substr($iso, 12, 2);
 880          $this->second = substr($iso, 15, 2);
 881          $this->timezone = substr($iso, 17);
 882      }
 883  
 884      function getIso()
 885      {
 886          return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second.$this->timezone;
 887      }
 888  
 889      function getXml()
 890      {
 891          return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>';
 892      }
 893  
 894      function getTimestamp()
 895      {
 896          return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);
 897      }
 898  }
 899  
 900  /**
 901   * IXR_Base64
 902   *
 903   * @package IXR
 904   * @since 1.5.0
 905   */
 906  class IXR_Base64
 907  {
 908      var $data;
 909  
 910      function __construct($data)
 911      {
 912          $this->data = $data;
 913      }
 914  
 915      function getXml()
 916      {
 917          return '<base64>'.base64_encode($this->data).'</base64>';
 918      }
 919  }
 920  
 921  /**
 922   * IXR_IntrospectionServer
 923   *
 924   * @package IXR
 925   * @since 1.5.0
 926   */
 927  class IXR_IntrospectionServer extends IXR_Server
 928  {
 929      var $signatures;
 930      var $help;
 931  
 932      function __construct()
 933      {
 934          $this->setCallbacks();
 935          $this->setCapabilities();
 936          $this->capabilities['introspection'] = array(
 937              'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html',
 938              'specVersion' => 1
 939          );
 940          $this->addCallback(
 941              'system.methodSignature',
 942              'this:methodSignature',
 943              array('array', 'string'),
 944              'Returns an array describing the return type and required parameters of a method'
 945          );
 946          $this->addCallback(
 947              'system.getCapabilities',
 948              'this:getCapabilities',
 949              array('struct'),
 950              'Returns a struct describing the XML-RPC specifications supported by this server'
 951          );
 952          $this->addCallback(
 953              'system.listMethods',
 954              'this:listMethods',
 955              array('array'),
 956              'Returns an array of available methods on this server'
 957          );
 958          $this->addCallback(
 959              'system.methodHelp',
 960              'this:methodHelp',
 961              array('string', 'string'),
 962              'Returns a documentation string for the specified method'
 963          );
 964      }
 965  
 966      function addCallback($method, $callback, $args, $help)
 967      {
 968          $this->callbacks[$method] = $callback;
 969          $this->signatures[$method] = $args;
 970          $this->help[$method] = $help;
 971      }
 972  
 973      function call($methodname, $args)
 974      {
 975          // Make sure it's in an array
 976          if ($args && !is_array($args)) {
 977              $args = array($args);
 978          }
 979  
 980          // Over-rides default call method, adds signature check
 981          if (!$this->hasMethod($methodname)) {
 982              return new IXR_Error(-32601, 'server error. requested method "'.$this->message->methodName.'" not specified.');
 983          }
 984          $method = $this->callbacks[$methodname];
 985          $signature = $this->signatures[$methodname];
 986          $returnType = array_shift($signature);
 987  
 988          // Check the number of arguments
 989          if (count($args) != count($signature)) {
 990              return new IXR_Error(-32602, 'server error. wrong number of method parameters');
 991          }
 992  
 993          // Check the argument types
 994          $ok = true;
 995          $argsbackup = $args;
 996          for ($i = 0, $j = count($args); $i < $j; $i++) {
 997              $arg = array_shift($args);
 998              $type = array_shift($signature);
 999              switch ($type) {
1000                  case 'int':
1001                  case 'i4':
1002                      if (is_array($arg) || !is_int($arg)) {
1003                          $ok = false;
1004                      }
1005                      break;
1006                  case 'base64':
1007                  case 'string':
1008                      if (!is_string($arg)) {
1009                          $ok = false;
1010                      }
1011                      break;
1012                  case 'boolean':
1013                      if ($arg !== false && $arg !== true) {
1014                          $ok = false;
1015                      }
1016                      break;
1017                  case 'float':
1018                  case 'double':
1019                      if (!is_float($arg)) {
1020                          $ok = false;
1021                      }
1022                      break;
1023                  case 'date':
1024                  case 'dateTime.iso8601':
1025                      if (!is_a($arg, 'IXR_Date')) {
1026                          $ok = false;
1027                      }
1028                      break;
1029              }
1030              if (!$ok) {
1031                  return new IXR_Error(-32602, 'server error. invalid method parameters');
1032              }
1033          }
1034          // It passed the test - run the "real" method call
1035          return parent::call($methodname, $argsbackup);
1036      }
1037  
1038      function methodSignature($method)
1039      {
1040          if (!$this->hasMethod($method)) {
1041              return new IXR_Error(-32601, 'server error. requested method "'.$method.'" not specified.');
1042          }
1043          // We should be returning an array of types
1044          $types = $this->signatures[$method];
1045          $return = array();
1046          foreach ($types as $type) {
1047              switch ($type) {
1048                  case 'string':
1049                      $return[] = 'string';
1050                      break;
1051                  case 'int':
1052                  case 'i4':
1053                      $return[] = 42;
1054                      break;
1055                  case 'double':
1056                      $return[] = 3.1415;
1057                      break;
1058                  case 'dateTime.iso8601':
1059                      $return[] = new IXR_Date(time());
1060                      break;
1061                  case 'boolean':
1062                      $return[] = true;
1063                      break;
1064                  case 'base64':
1065                      $return[] = new IXR_Base64('base64');
1066                      break;
1067                  case 'array':
1068                      $return[] = array('array');
1069                      break;
1070                  case 'struct':
1071                      $return[] = array('struct' => 'struct');
1072                      break;
1073              }
1074          }
1075          return $return;
1076      }
1077  
1078      function methodHelp($method)
1079      {
1080          return $this->help[$method];
1081      }
1082  }
1083  
1084  /**
1085   * IXR_ClientMulticall
1086   *
1087   * @package IXR
1088   * @since 1.5.0
1089   */
1090  class IXR_ClientMulticall extends IXR_Client
1091  {
1092      var $calls = array();
1093  
1094      function __construct($server, $path = false, $port = 80)
1095      {
1096          parent::__construct($server, $path, $port);
1097          $this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)';
1098      }
1099  
1100      function addCall()
1101      {
1102          $args = func_get_args();
1103          $methodName = array_shift($args);
1104          $struct = array(
1105              'methodName' => $methodName,
1106              'params' => $args
1107          );
1108          $this->calls[] = $struct;
1109      }
1110  
1111      function query()
1112      {
1113          // Prepare multicall, then call the parent::query() method
1114          return parent::query('system.multicall', $this->calls);
1115      }
1116  }
1117  
1118  /**
1119   * Client for communicating with a XML-RPC Server over HTTPS.
1120   *
1121   * @author Jason Stirk <jstirk@gmm.com.au> (@link http://blog.griffin.homelinux.org/projects/xmlrpc/)
1122   * @version 0.2.0 26May2005 08:34 +0800
1123   * @copyright (c) 2004-2005 Jason Stirk
1124   * @package IXR
1125   */
1126  class IXR_ClientSSL extends IXR_Client
1127  {
1128      /**
1129       * Filename of the SSL Client Certificate
1130       * @access private
1131       * @since 0.1.0
1132       * @var string
1133       */
1134      var $_certFile;
1135  
1136      /**
1137       * Filename of the SSL CA Certificate
1138       * @access private
1139       * @since 0.1.0
1140       * @var string
1141       */
1142      var $_caFile;
1143  
1144      /**
1145       * Filename of the SSL Client Private Key
1146       * @access private
1147       * @since 0.1.0
1148       * @var string
1149       */
1150      var $_keyFile;
1151  
1152      /**
1153       * Passphrase to unlock the private key
1154       * @access private
1155       * @since 0.1.0
1156       * @var string
1157       */
1158      var $_passphrase;
1159  
1160      /**
1161       * Constructor
1162       * @param string $server URL of the Server to connect to
1163       * @since 0.1.0
1164       */
1165      function __construct($server, $path = false, $port = 443, $timeout = false)
1166      {
1167          parent::__construct($server, $path, $port, $timeout);
1168          $this->useragent = 'The Incutio XML-RPC PHP Library for SSL';
1169  
1170          // Set class fields
1171          $this->_certFile=false;
1172          $this->_caFile=false;
1173          $this->_keyFile=false;
1174          $this->_passphrase='';
1175      }
1176  
1177      /**
1178       * Set the client side certificates to communicate with the server.
1179       *
1180       * @since 0.1.0
1181       * @param string $certificateFile Filename of the client side certificate to use
1182       * @param string $keyFile Filename of the client side certificate's private key
1183       * @param string $keyPhrase Passphrase to unlock the private key
1184       */
1185      function setCertificate($certificateFile, $keyFile, $keyPhrase='')
1186      {
1187          // Check the files all exist
1188          if (is_file($certificateFile)) {
1189              $this->_certFile = $certificateFile;
1190          } else {
1191              die('Could not open certificate: ' . $certificateFile);
1192          }
1193  
1194          if (is_file($keyFile)) {
1195              $this->_keyFile = $keyFile;
1196          } else {
1197              die('Could not open private key: ' . $keyFile);
1198          }
1199  
1200          $this->_passphrase=(string)$keyPhrase;
1201      }
1202  
1203      function setCACertificate($caFile)
1204      {
1205          if (is_file($caFile)) {
1206              $this->_caFile = $caFile;
1207          } else {
1208              die('Could not open CA certificate: ' . $caFile);
1209          }
1210      }
1211  
1212      /**
1213       * Sets the connection timeout (in seconds)
1214       * @param int $newTimeOut Timeout in seconds
1215       * @returns void
1216       * @since 0.1.2
1217       */
1218      function setTimeOut($newTimeOut)
1219      {
1220          $this->timeout = (int)$newTimeOut;
1221      }
1222  
1223      /**
1224       * Returns the connection timeout (in seconds)
1225       * @returns int
1226       * @since 0.1.2
1227       */
1228      function getTimeOut()
1229      {
1230          return $this->timeout;
1231      }
1232  
1233      /**
1234       * Set the query to send to the XML-RPC Server
1235       * @since 0.1.0
1236       */
1237      function query()
1238      {
1239          $args = func_get_args();
1240          $method = array_shift($args);
1241          $request = new IXR_Request($method, $args);
1242          $length = $request->getLength();
1243          $xml = $request->getXml();
1244  
1245          if ($this->debug) {
1246              echo '<pre>'.htmlspecialchars($xml)."\n</pre>\n\n";
1247          }
1248  
1249          //This is where we deviate from the normal query()
1250          //Rather than open a normal sock, we will actually use the cURL
1251          //extensions to make the calls, and handle the SSL stuff.
1252  
1253          //Since 04Aug2004 (0.1.3) - Need to include the port (duh...)
1254          //Since 06Oct2004 (0.1.4) - Need to include the colon!!!
1255          //        (I swear I've fixed this before... ESP in live... But anyhu...)
1256          $curl=curl_init('https://' . $this->server . ':' . $this->port . $this->path);
1257          curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
1258  
1259          //Since 23Jun2004 (0.1.2) - Made timeout a class field
1260          curl_setopt($curl, CURLOPT_TIMEOUT, $this->timeout);
1261  
1262          if ($this->debug) {
1263              curl_setopt($curl, CURLOPT_VERBOSE, 1);
1264          }
1265  
1266          curl_setopt($curl, CURLOPT_HEADER, 1);
1267          curl_setopt($curl, CURLOPT_POST, 1);
1268          curl_setopt($curl, CURLOPT_POSTFIELDS, $xml);
1269          curl_setopt($curl, CURLOPT_PORT, $this->port);
1270          curl_setopt($curl, CURLOPT_HTTPHEADER, array(
1271                                      "Content-Type: text/xml",
1272                                      "Content-length: {$length}"));
1273  
1274          // Process the SSL certificates, etc. to use
1275          if (!($this->_certFile === false)) {
1276              // We have a certificate file set, so add these to the cURL handler
1277              curl_setopt($curl, CURLOPT_SSLCERT, $this->_certFile);
1278              curl_setopt($curl, CURLOPT_SSLKEY, $this->_keyFile);
1279  
1280              if ($this->debug) {
1281                  echo "SSL Cert at : " . $this->_certFile . "\n";
1282                  echo "SSL Key at : " . $this->_keyFile . "\n";
1283              }
1284  
1285              // See if we need to give a passphrase
1286              if (!($this->_passphrase === '')) {
1287                  curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $this->_passphrase);
1288              }
1289  
1290              if ($this->_caFile === false) {
1291                  // Don't verify their certificate, as we don't have a CA to verify against
1292                  curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
1293              } else {
1294                  // Verify against a CA
1295                  curl_setopt($curl, CURLOPT_CAINFO, $this->_caFile);
1296              }
1297          }
1298  
1299          // Call cURL to do it's stuff and return us the content
1300          $contents = curl_exec($curl);
1301          curl_close($curl);
1302  
1303          // Check for 200 Code in $contents
1304          if (!strstr($contents, '200 OK')) {
1305              //There was no "200 OK" returned - we failed
1306              $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200');
1307              return false;
1308          }
1309  
1310          if ($this->debug) {
1311              echo '<pre>'.htmlspecialchars($contents)."\n</pre>\n\n";
1312          }
1313          // Now parse what we've got back
1314          // Since 20Jun2004 (0.1.1) - We need to remove the headers first
1315          // Why I have only just found this, I will never know...
1316          // So, remove everything before the first <
1317          $contents = substr($contents,strpos($contents, '<'));
1318  
1319          $this->message = new IXR_Message($contents);
1320          if (!$this->message->parse()) {
1321              // XML error
1322              $this->error = new IXR_Error(-32700, 'parse error. not well formed');
1323              return false;
1324          }
1325          // Is the message a fault?
1326          if ($this->message->messageType == 'fault') {
1327              $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);
1328              return false;
1329          }
1330  
1331          // Message must be OK
1332          return true;
1333      }
1334  }
1335  
1336  /**
1337   * Extension of the {@link IXR_Server} class to easily wrap objects.
1338   *
1339   * Class is designed to extend the existing XML-RPC server to allow the
1340   * presentation of methods from a variety of different objects via an
1341   * XML-RPC server.
1342   * It is intended to assist in organization of your XML-RPC methods by allowing
1343   * you to "write once" in your existing model classes and present them.
1344   *
1345   * @author Jason Stirk <jstirk@gmm.com.au>
1346   * @version 1.0.1 19Apr2005 17:40 +0800
1347   * @copyright Copyright (c) 2005 Jason Stirk
1348   * @package IXR
1349   */
1350  class IXR_ClassServer extends IXR_Server
1351  {
1352      var $_objects;
1353      var $_delim;
1354  
1355      function __construct($delim = '.', $wait = false)
1356      {
1357          $this->IXR_Server(array(), false, $wait);
1358          $this->_delimiter = $delim;
1359          $this->_objects = array();
1360      }
1361  
1362      function addMethod($rpcName, $functionName)
1363      {
1364          $this->callbacks[$rpcName] = $functionName;
1365      }
1366  
1367      function registerObject($object, $methods, $prefix=null)
1368      {
1369          if (is_null($prefix))
1370          {
1371              $prefix = get_class($object);
1372          }
1373          $this->_objects[$prefix] = $object;
1374  
1375          // Add to our callbacks array
1376          foreach($methods as $method)
1377          {
1378              if (is_array($method))
1379              {
1380                  $targetMethod = $method[0];
1381                  $method = $method[1];
1382              }
1383              else
1384              {
1385                  $targetMethod = $method;
1386              }
1387              $this->callbacks[$prefix . $this->_delimiter . $method]=array($prefix, $targetMethod);
1388          }
1389      }
1390  
1391      function call($methodname, $args)
1392      {
1393          if (!$this->hasMethod($methodname)) {
1394              return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.');
1395          }
1396          $method = $this->callbacks[$methodname];
1397  
1398          // Perform the callback and send the response
1399          if (count($args) == 1) {
1400              // If only one parameter just send that instead of the whole array
1401              $args = $args[0];
1402          }
1403  
1404          // See if this method comes from one of our objects or maybe self
1405          if (is_array($method) || (substr($method, 0, 5) == 'this:')) {
1406              if (is_array($method)) {
1407                  $object=$this->_objects[$method[0]];
1408                  $method=$method[1];
1409              } else {
1410                  $object=$this;
1411                  $method = substr($method, 5);
1412              }
1413  
1414              // It's a class method - check it exists
1415              if (!method_exists($object, $method)) {
1416                  return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.');
1417              }
1418  
1419              // Call the method
1420              $result = $object->$method($args);
1421          } else {
1422              // It's a function - does it exist?
1423              if (!function_exists($method)) {
1424                  return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.');
1425              }
1426  
1427              // Call the function
1428              $result = $method($args);
1429          }
1430          return $result;
1431      }
1432  }

title

Description

title

Description

title

Description

title

title

Body