Ticket #3906: class-IXR.php

File class-IXR.php, 27.1 kB (added by momo360modena, 2 years ago)
Line 
1 <?php
2 /*
3    IXR - The Inutio XML-RPC Library - (c) Incutio Ltd 2002-2005
4    Version 1.7 (beta) - Simon Willison, 23rd May 2005
5    Site:   http://scripts.incutio.com/xmlrpc/
6    Manual: http://scripts.incutio.com/xmlrpc/manual.php
7    Made available under the BSD License: http://www.opensource.org/licenses/bsd-license.php
8 */
9
10 class IXR_Value {
11     var $data;
12     var $type;
13     function IXR_Value ($data, $type = false) {
14         $this->data = $data;
15         if (!$type) {
16             $type = $this->calculateType();
17         }
18         $this->type = $type;
19         if ($type == 'struct') {
20             /* Turn all the values in the array in to new IXR_Value objects */
21             foreach ($this->data as $key => $value) {
22                 $this->data[$key] = new IXR_Value($value);
23             }
24         }
25         if ($type == 'array') {
26             for ($i = 0, $j = count($this->data); $i < $j; $i++) {
27                 $this->data[$i] = new IXR_Value($this->data[$i]);
28             }
29         }
30     }
31     function calculateType() {
32         if ($this->data === true || $this->data === false) {
33             return 'boolean';
34         }
35         if (is_integer($this->data)) {
36             return 'int';
37         }
38         if (is_double($this->data)) {
39             return 'double';
40         }
41         // Deal with IXR object types base64 and date
42         if (is_object($this->data) && is_a($this->data, 'IXR_Date')) {
43             return 'date';
44         }
45         if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) {
46             return 'base64';
47         }
48         // If it is a normal PHP object convert it in to a struct
49         if (is_object($this->data)) {
50             
51             $this->data = get_object_vars($this->data);
52             return 'struct';
53         }
54         if (!is_array($this->data)) {
55             return 'string';
56         }
57         /* We have an array - is it an array or a struct ? */
58         if ($this->isStruct($this->data)) {
59             return 'struct';
60         } else {
61             return 'array';
62         }
63     }
64     function getXml() {
65         /* Return XML for this value */
66         switch ($this->type) {
67             case 'boolean':
68                 return '<boolean>'.(($this->data) ? '1' : '0').'</boolean>';
69                 break;
70             case 'int':
71                 return '<int>'.$this->data.'</int>';
72                 break;
73             case 'double':
74                 return '<double>'.$this->data.'</double>';
75                 break;
76             case 'string':
77                 return '<string>'.htmlspecialchars($this->data).'</string>';
78                 break;
79             case 'array':
80                 $return = '<array><data>'."\n";
81                 foreach ($this->data as $item) {
82                     $return .= '  <value>'.$item->getXml()."</value>\n";
83                 }
84                 $return .= '</data></array>';
85                 return $return;
86                 break;
87             case 'struct':
88                 $return = '<struct>'."\n";
89                 foreach ($this->data as $name => $value) {
90                     $name = htmlspecialchars($name);
91                     $return .= "  <member><name>$name</name><value>";
92                     $return .= $value->getXml()."</value></member>\n";
93                 }
94                 $return .= '</struct>';
95                 return $return;
96                 break;
97             case 'date':
98             case 'base64':
99                 return $this->data->getXml();
100                 break;
101         }
102         return false;
103     }
104     function isStruct($array) {
105         /* Nasty function to check if an array is a struct or not */
106         $expected = 0;
107         foreach ($array as $key => $value) {
108             if ((string)$key != (string)$expected) {
109                 return true;
110             }
111             $expected++;
112         }
113         return false;
114     }
115 }
116
117
118 class IXR_Message {
119     var $message;
120     var $messageType// methodCall / methodResponse / fault
121     var $faultCode;
122     var $faultString;
123     var $methodName;
124     var $params;
125     // Current variable stacks
126     var $_arraystructs = array();   // The stack used to keep track of the current array/struct
127     var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array
128     var $_currentStructName = array();  // A stack as well
129     var $_param;
130     var $_value;
131     var $_currentTag;
132     var $_currentTagContents;
133     // The XML parser
134     var $_parser;
135     function IXR_Message ($message) {
136         $this->message = $message;
137     }
138     
139
140
141     function parse() {
142         /*
143         // first remove the XML declaration
144         $this->message = preg_replace('/<\?xml(.*)?\?'.'>/', '', $this->message);
145         if (trim($this->message) == '') {
146         return false;
147         }
148         $this->_parser = xml_parser_create(); */
149
150         // first remove the XML declaration
151         $rx = '/<?xml.*encoding=[\'"](.*?)[\'"].*?>/m';
152         
153         if ( preg_match($rx, $this->message, $m) ) {
154                 $encoding = strtoupper($m[1]);
155         }
156         else {
157                 $encoding = 'UTF-8';
158         }
159         $this->_parser = xml_parser_create($encoding);
160                 
161         // Set XML parser to take the case of tags in to account
162         xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
163         // Set XML parser callback functions
164         xml_set_object($this->_parser, $this);
165         xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');
166         xml_set_character_data_handler($this->_parser, 'cdata');
167         if (!xml_parse($this->_parser, $this->message)) {
168             /* die(sprintf('XML error: %s at line %d',
169                 xml_error_string(xml_get_error_code($this->_parser)),
170                 xml_get_current_line_number($this->_parser))); */
171             return false;
172         }
173         xml_parser_free($this->_parser);
174         // Grab the error messages, if any
175         if ($this->messageType == 'fault') {
176             $this->faultCode = $this->params[0]['faultCode'];
177             $this->faultString = $this->params[0]['faultString'];
178         }
179         return true;
180     }
181     function tag_open($parser, $tag, $attr) {
182         $this->_currentTagContents = '';
183         $this->currentTag = $tag;
184         switch($tag) {
185             case 'methodCall':
186             case 'methodResponse':
187             case 'fault':
188                 $this->messageType = $tag;
189                 break;
190             /* Deal with stacks of arrays and structs */
191             case 'data':    // data is to all intents and puposes more interesting than array
192                 $this->_arraystructstypes[] = 'array';
193                 $this->_arraystructs[] = array();
194                 break;
195             case 'struct':
196                 $this->_arraystructstypes[] = 'struct';
197                 $this->_arraystructs[] = array();
198                 break;
199         }
200     }
201     function cdata($parser, $cdata) {
202         $this->_currentTagContents .= $cdata;
203     }
204     function tag_close($parser, $tag) {
205         $valueFlag = false;
206         switch($tag) {
207             case 'int':
208             case 'i4':
209                 $value = (int) trim($this->_currentTagContents);
210                 $valueFlag = true;
211                 break;
212             case 'double':
213                 $value = (double) trim($this->_currentTagContents);
214                 $valueFlag = true;
215                 break;
216             case 'string':
217                 $value = $this->_currentTagContents;
218                 $valueFlag = true;
219                 break;
220             case 'dateTime.iso8601':
221                 $value = new IXR_Date(trim($this->_currentTagContents));
222                 // $value = $iso->getTimestamp();
223                 $valueFlag = true;
224                 break;
225             case 'value':
226                 // "If no type is indicated, the type is string."
227                 if (trim($this->_currentTagContents) != '') {
228                     $value = (string)$this->_currentTagContents;
229                     $valueFlag = true;
230                 }
231                 break;
232             case 'boolean':
233                 $value = (boolean) trim($this->_currentTagContents);
234                 $valueFlag = true;
235                 break;
236             case 'base64':
237                 $value = base64_decode( trim( $this->_currentTagContents ) );
238                 $valueFlag = true;
239                 break;
240             /* Deal with stacks of arrays and structs */
241             case 'data':
242             case 'struct':
243                 $value = array_pop($this->_arraystructs);
244                 array_pop($this->_arraystructstypes);
245                 $valueFlag = true;
246                 break;
247             case 'member':
248                 array_pop($this->_currentStructName);
249                 break;
250             case 'name':
251                 $this->_currentStructName[] = trim($this->_currentTagContents);
252                 break;
253             case 'methodName':
254                 $this->methodName = trim($this->_currentTagContents);
255                 break;
256         }
257         if ($valueFlag) {
258             if (count($this->_arraystructs) > 0) {
259                 // Add value to struct or array
260                 if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {
261                     // Add to struct
262                     $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value;
263                 } else {
264                     // Add to array
265                     $this->_arraystructs[count($this->_arraystructs)-1][] = $value;
266                 }
267             } else {
268                 // Just add as a paramater
269                 $this->params[] = $value;
270             }
271         }
272         $this->_currentTagContents = '';
273     }       
274 }
275
276
277 class IXR_Server {
278     var $data;
279     var $callbacks = array();
280     var $message;
281     var $capabilities;
282     function IXR_Server($callbacks = false, $data = false) {
283         $this->setCapabilities();
284         if ($callbacks) {
285             $this->callbacks = $callbacks;
286         }
287         $this->setCallbacks();
288         $this->serve($data);
289     }
290     function serve($data = false) {
291         if (!$data) {
292             global $HTTP_RAW_POST_DATA;
293             if (!$HTTP_RAW_POST_DATA) {
294                die('XML-RPC server accepts POST requests only.');
295             }
296             $data = $HTTP_RAW_POST_DATA;
297         }
298         $this->message = new IXR_Message($data);
299         if (!$this->message->parse()) {
300             $this->error(-32700, 'parse error. not well formed');
301         }
302         if ($this->message->messageType != 'methodCall') {
303             $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall');
304         }
305         $result = $this->call($this->message->methodName, $this->message->params);
306         // Is the result an error?
307         if (is_a($result, 'IXR_Error')) {
308             $this->error($result);
309         }
310         // Encode the result
311         $r = new IXR_Value($result);
312         $resultxml = $r->getXml();
313         // Create the XML
314         $xml = <<<EOD
315 <methodResponse>
316   <params>
317     <param>
318       <value>
319         $resultxml
320       </value>
321     </param>
322   </params>
323 </methodResponse>
324
325 EOD;
326         // Send it
327         $this->output($xml);
328     }
329     function call($methodname, $args) {
330         if (!$this->hasMethod($methodname)) {
331             return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.');
332         }
333         $method = $this->callbacks[$methodname];
334         // Perform the callback and send the response
335         if (count($args) == 1) {
336             // If only one paramater just send that instead of the whole array
337             $args = $args[0];
338         }
339         // Are we dealing with a function or a method?
340         if (substr($method, 0, 5) == 'this:') {
341             // It's a class method - check it exists
342             $method = substr($method, 5);
343             if (!method_exists($this, $method)) {
344                 return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.');
345             }
346             // Call the method
347             $result = $this->$method($args);
348         } else {
349             // It's a function - does it exist?
350             if (is_array($method)) {
351                 if (!method_exists($method[0], $method[1])) {
352                 return new IXR_Error(-32601, 'server error. requested object method "'.$method[1].'" does not exist.');
353                 }
354             } else if (!function_exists($method)) {
355                 return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.');
356             }
357             // Call the function
358             $result = call_user_func($method, $args);
359         }
360         return $result;
361     }
362
363     function error($error, $message = false) {
364         // Accepts either an error object or an error code and message
365         if ($message && !is_object($error)) {
366             $error = new IXR_Error($error, $message);
367         }
368         $this->output($error->getXml());
369     }
370     function output($xml) {
371         $xml = '<?xml version="1.0"?>'."\n".$xml;
372         $length = strlen($xml);
373         header('Connection: close');
374         header('Content-Length: '.$length);
375         header('Content-Type: text/xml');
376         header('Date: '.date('r'));
377         echo $xml;
378         exit;
379     }
380     function hasMethod($method) {
381         return in_array($method, array_keys($this->callbacks));
382     }
383     function setCapabilities() {
384         // Initialises capabilities array
385         $this->capabilities = array(
386             'xmlrpc' => array(
387                 'specUrl' => 'http://www.xmlrpc.com/spec',
388                 'specVersion' => 1
389             ),
390             'faults_interop' => array(
391                 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
392                 'specVersion' => 20010516
393             ),
394             'system.multicall' => array(
395                 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
396                 'specVersion' => 1
397             ),
398         );   
399     }
400     function getCapabilities($args) {
401         return $this->capabilities;
402     }
403     function setCallbacks() {
404         $this->callbacks['system.getCapabilities'] = 'this:getCapabilities';
405         $this->callbacks['system.listMethods'] = 'this:listMethods';
406         $this->callbacks['system.multicall'] = 'this:multiCall';
407     }
408     function listMethods($args) {
409         // Returns a list of methods - uses array_reverse to ensure user defined
410         // methods are listed before server defined methods
411         return array_reverse(array_keys($this->callbacks));
412     }
413     function multiCall($methodcalls) {
414         // See http://www.xmlrpc.com/discuss/msgReader$1208
415         $return = array();
416         foreach ($methodcalls as $call) {
417             $method = $call['methodName'];
418             $params = $call['params'];
419             if ($method == 'system.multicall') {
420                 $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden');
421             } else {
422                 $result = $this->call($method, $params);
423             }
424             if (is_a($result, 'IXR_Error')) {
425                 $return[] = array(
426                     'faultCode' => $result->code,
427                     'faultString' => $result->message
428                 );
429             } else {
430                 $return[] = array($result);
431             }
432         }
433         return $return;
434     }
435 }
436
437 class IXR_Request {
438     var $method;
439     var $args;
440     var $xml;
441     function IXR_Request($method, $args) {
442         $this->method = $method;
443         $this->args = $args;
444         $this->xml = <<<EOD
445 <?xml version="1.0"?>
446 <methodCall>
447 <methodName>{$this->method}</methodName>
448 <params>
449
450 EOD;
451         foreach ($this->args as $arg) {
452             $this->xml .= '<param><value>';
453             $v = new IXR_Value($arg);
454             $this->xml .= $v->getXml();
455             $this->xml .= "</value></param>\n";
456         }
457         $this->xml .= '</params></methodCall>';
458     }
459     function getLength() {
460         return strlen($this->xml);
461     }
462     function getXml() {
463         return $this->xml;
464     }
465 }
466
467
468 class IXR_Client {
469     var $server;
470     var $port;
471     var $path;
472     var $useragent;
473     var $response;
474     var $message = false;
475     var $debug = false;
476     var $timeout;
477     // Storage place for an error message
478     var $error = false;
479     function IXR_Client($server, $path = false, $port = 80, $timeout = false) {
480         if (!$path) {
481             // Assume we have been given a URL instead
482             $bits = parse_url($server);
483             $this->server = $bits['host'];
484             $this->port = is