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