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

Revision 3279, 26.9 kB (checked in by ryan, 3 years ago)

Add timezone to iso8601 timestamp. Props devlogic. fixes #2036

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
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     function parse() {
139         // first remove the XML declaration
140         $this->message = preg_replace('/<\?xml(.*)?\?'.'>/', '', $this->message);
141         if (trim($this->message) == '') {
142             return false;
143         }
144         $this->_parser = xml_parser_create();
145         // Set XML parser to take the case of tags in to account
146         xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
147         // Set XML parser callback functions
148         xml_set_object($this->_parser, $this);
149         xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');
150         xml_set_character_data_handler($this->_parser, 'cdata');
151         if (!xml_parse($this->_parser, $this->message)) {
152             /* die(sprintf('XML error: %s at line %d',
153                 xml_error_string(xml_get_error_code($this->_parser)),
154                 xml_get_current_line_number($this->_parser))); */
155             return false;
156         }
157         xml_parser_free($this->_parser);
158         // Grab the error messages, if any
159         if ($this->messageType == 'fault') {
160             $this->faultCode = $this->params[0]['faultCode'];
161             $this->faultString = $this->params[0]['faultString'];
162         }
163         return true;
164     }
165     function tag_open($parser, $tag, $attr) {
166         $this->_currentTagContents = '';
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                 $valueFlag = true;
195                 break;
196             case 'double':
197                 $value = (double) trim($this->_currentTagContents);
198                 $valueFlag = true;
199                 break;
200             case 'string':
201                 $value = $this->_currentTagContents;
202                 $valueFlag = true;
203                 break;
204             case 'dateTime.iso8601':
205                 $value = new IXR_Date(trim($this->_currentTagContents));
206                 // $value = $iso->getTimestamp();
207                 $valueFlag = true;
208                 break;
209             case 'value':
210                 // "If no type is indicated, the type is string."
211                 if (trim($this->_currentTagContents) != '') {
212                     $value = (string)$this->_currentTagContents;
213                     $valueFlag = true;
214                 }
215                 break;
216             case 'boolean':
217                 $value = (boolean) trim($this->_currentTagContents);
218                 $valueFlag = true;
219                 break;
220             case 'base64':
221                 $value = base64_decode( trim( $this->_currentTagContents ) );
222                 $valueFlag = true;
223                 break;
224             /* Deal with stacks of arrays and structs */
225             case 'data':
226             case 'struct':
227                 $value = array_pop($this->_arraystructs);
228                 array_pop($this->_arraystructstypes);
229                 $valueFlag = true;
230                 break;
231             case 'member':
232                 array_pop($this->_currentStructName);
233                 break;
234             case 'name':
235                 $this->_currentStructName[] = trim($this->_currentTagContents);
236                 break;
237             case 'methodName':
238                 $this->methodName = trim($this->_currentTagContents);
239                 break;
240         }
241         if ($valueFlag) {
242             if (count($this->_arraystructs) > 0) {
243                 // Add value to struct or array
244                 if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {
245                     // Add to struct
246                     $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value;
247                 } else {
248                     // Add to array
249                     $this->_arraystructs[count($this->_arraystructs)-1][] = $value;
250                 }
251             } else {
252                 // Just add as a paramater
253                 $this->params[] = $value;
254             }
255         }
256         $this->_currentTagContents = '';
257     }       
258 }
259
260
261 class IXR_Server {
262     var $data;
263     var $callbacks = array();
264     var $message;
265     var $capabilities;
266     function IXR_Server($callbacks = false, $data = false) {
267         $this->setCapabilities();
268         if ($callbacks) {
269             $this->callbacks = $callbacks;
270         }
271         $this->setCallbacks();
272         $this->serve($data);
273     }
274     function serve($data = false) {
275         if (!$data) {
276             global $HTTP_RAW_POST_DATA;
277             if (!$HTTP_RAW_POST_DATA) {
278                die('XML-RPC server accepts POST requests only.');
279             }
280             $data = $HTTP_RAW_POST_DATA;
281         }
282         $this->message = new IXR_Message($data);
283         if (!$this->message->parse()) {
284             $this->error(-32700, 'parse error. not well formed');
285         }
286         if ($this->message->messageType != 'methodCall') {
287             $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall');
288         }
289         $result = $this->call($this->message->methodName, $this->message->params);
290         // Is the result an error?
291         if (is_a($result, 'IXR_Error')) {
292             $this->error($result);
293         }
294         // Encode the result
295         $r = new IXR_Value($result);
296         $resultxml = $r->getXml();
297         // Create the XML
298         $xml = <<<EOD
299 <methodResponse>
300   <params>
301     <param>
302       <value>
303         $resultxml
304       </value>
305     </param>
306   </params>
307 </methodResponse>
308
309 EOD;
310         // Send it
311         $this->output($xml);
312     }
313     function call($methodname, $args) {
314         if (!$this->hasMethod($methodname)) {
315             return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.');
316         }
317         $method = $this->callbacks[$methodname];
318         // Perform the callback and send the response
319         if (count($args) == 1) {
320             // If only one paramater just send that instead of the whole array
321             $args = $args[0];
322         }
323         // Are we dealing with a function or a method?
324         if (substr($method, 0, 5) == 'this:') {
325             // It's a class method - check it exists
326             $method = substr($method, 5);
327             if (!method_exists($this, $method)) {
328                 return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.');
329             }
330             // Call the method
331             $result = $this->$method($args);
332         } else {
333             // It's a function - does it exist?
334             if (is_array($method)) {
335                 if (!method_exists($method[0], $method[1])) {
336                 return new IXR_Error(-32601, 'server error. requested object method "'.$method[1].'" does not exist.');
337                 }
338             } else if (!function_exists($method)) {
339                 return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.');
340             }
341             // Call the function
342             $result = call_user_func($method, $args);
343         }
344         return $result;
345     }
346
347     function error($error, $message = false) {
348         // Accepts either an error object or an error code and message
349         if ($message && !is_object($error)) {
350             $error = new IXR_Error($error, $message);
351         }
352         $this->output($error->getXml());
353     }
354     function output($xml) {
355         $xml = '<?xml version="1.0"?>'."\n".$xml;
356         $length = strlen($xml);
357         header('Connection: close');
358         header('Content-Length: '.$length);
359         header('Content-Type: text/xml');
360         header('Date: '.date('r'));
361         echo $xml;
362         exit;
363     }
364     function hasMethod($method) {
365         return in_array($method, array_keys($this->callbacks));
366     }
367     function setCapabilities() {
368         // Initialises capabilities array
369         $this->capabilities = array(
370             'xmlrpc' => array(
371                 'specUrl' => 'http://www.xmlrpc.com/spec',
372                 'specVersion' => 1
373             ),
374             'faults_interop' => array(
375                 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
376                 'specVersion' => 20010516
377             ),
378             'system.multicall' => array(
379                 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
380                 'specVersion' => 1
381             ),
382         );   
383     }
384     function getCapabilities($args) {
385         return $this->capabilities;
386     }
387     function setCallbacks() {
388         $this->callbacks['system.getCapabilities'] = 'this:getCapabilities';
389         $this->callbacks['system.listMethods'] = 'this:listMethods';
390         $this->callbacks['system.multicall'] = 'this:multiCall';
391     }
392     function listMethods($args) {
393         // Returns a list of methods - uses array_reverse to ensure user defined
394         // methods are listed before server defined methods
395         return array_reverse(array_keys($this->callbacks));
396     }
397     function multiCall($methodcalls) {
398         // See http://www.xmlrpc.com/discuss/msgReader$1208
399         $return = array();
400         foreach ($methodcalls as $call) {
401             $method = $call['methodName'];
402             $params = $call['params'];
403             if ($method == 'system.multicall') {
404                 $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden');
405             } else {
406                 $result = $this->call($method, $params);
407             }
408             if (is_a($result, 'IXR_Error')) {
409                 $return[] = array(
410                     'faultCode' => $result->code,
411                     'faultString' => $result->message
412                 );
413             } else {
414                 $return[] = array($result);
415             }
416         }
417         return $return;
418     }
419 }
420
421 class IXR_Request {
422     var $method;
423     var $args;
424     var $xml;
425     function IXR_Request($method, $args) {
426         $this->method = $method;
427         $this->args = $args;
428         $this->xml = <<<EOD
429 <?xml version="1.0"?>
430 <methodCall>
431 <methodName>{$this->method}</methodName>
432 <params>
433
434 EOD;
435         foreach ($this->args as $arg) {
436             $this->xml .= '<param><value>';
437             $v = new IXR_Value($arg);
438             $this->xml .= $v->getXml();
439             $this->xml .= "</value></param>\n";
440         }
441         $this->xml .= '</params></methodCall>';
442     }
443     function getLength() {
444         return strlen($this->xml);
445     }
446     function getXml() {
447         return $this->xml;
448     }
449 }
450
451
452 class IXR_Client {
453     var $server;
454     var $port;
455     var $path;
456     var $useragent;
457     var $response;
458     var $message = false;
459     var $debug = false;
460     var $timeout;
461     // Storage place for an error message
462     var $error = false;
463     function IXR_Client($server, $path = false, $port = 80, $timeout = false) {
464         if (!$path) {
465             // Assume we have been given a URL instead
466             $bits = parse_url($server);
467             $this->server = $bits['host'];
468             $this->port = isset($bits['port']) ? $bits['port'] : 80;
469             $this->path = isset($bits[