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