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