root/devel/mod/rpc/lib/IXR_Library.inc.php

Revision 1153, 27.3 kB (checked in by sven, 2 years ago)

rpc - work around php 5.2.2 bug (http://bugs.php.net/bug.php?id=41293)

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