root/branches/1.5/wp-includes/class-IXR.php

Revision 2139, 27.5 kB (checked in by saxmatt, 4 years ago)

Trim before decoding - http://mosquito.wordpress.org/view.php?id=654

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