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