Textpattern PHP Cross Reference Content Management Systems

Source: /textpattern/lib/IXRClass.php - 1400 lines - 42455 bytes - Summary - Text - Print

Description: IXR - The Incutio XML-RPC Library

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

title

Description

title

Description

title

Description

title

title

Body