root/trunk/scripts/ixr-library.inc.php

Revision 1688, 26.9 KB (checked in by driehle, 11 months ago)

Converted all files to UTF-8 and added accept-charset="UTF-8" to most forms.

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
12class 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            $this->data = get_object_vars($this->data);
53            return 'struct';
54        }
55        if (!is_array($this->data)) {
56            return 'string';
57        }
58        /* We have an array - is it an array or a struct ? */
59        if ($this->isStruct($this->data)) {
60            return 'struct';
61        } else {
62            return 'array';
63        }
64    }
65    function getXml() {
66        /* Return XML for this value */
67        switch ($this->type) {
68            case 'boolean':
69                return '<boolean>'.(($this->data) ? '1' : '0').'</boolean>';
70                break;
71            case 'int':
72                return '<int>'.$this->data.'</int>';
73                break;
74            case 'double':
75                return '<double>'.$this->data.'</double>';
76                break;
77            case 'string':
78                return '<string>'.htmlspecialchars($this->data).'</string>';
79                break;
80            case 'array':
81                $return = '<array><data>'."\n";
82                foreach ($this->data as $item) {
83                    $return .= '  <value>'.$item->getXml()."</value>\n";
84                }
85                $return .= '</data></array>';
86                return $return;
87                break;
88            case 'struct':
89                $return = '<struct>'."\n";
90                foreach ($this->data as $name => $value) {
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
118class 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->currentTag = $tag;
167        switch($tag) {
168            case 'methodCall':
169            case 'methodResponse':
170            case 'fault':
171                $this->messageType = $tag;
172                break;
173            /* Deal with stacks of arrays and structs */
174            case 'data':    // data is to all intents and puposes more interesting than array
175                $this->_arraystructstypes[] = 'array';
176                $this->_arraystructs[] = array();
177                break;
178            case 'struct':
179                $this->_arraystructstypes[] = 'struct';
180                $this->_arraystructs[] = array();
181                break;
182        }
183    }
184    function cdata($parser, $cdata) {
185        $this->_currentTagContents .= $cdata;
186    }
187    function tag_close($parser, $tag) {
188        $valueFlag = false;
189        switch($tag) {
190            case 'int':
191            case 'i4':
192                $value = (int)trim($this->_currentTagContents);
193                $this->_currentTagContents = '';
194                $valueFlag = true;
195                break;
196            case 'double':
197                $value = (double)trim($this->_currentTagContents);
198                $this->_currentTagContents = '';
199                $valueFlag = true;
200                break;
201            case 'string':
202                $value = (string)trim($this->_currentTagContents);
203                $this->_currentTagContents = '';
204                $valueFlag = true;
205                break;
206            case 'dateTime.iso8601':
207                $value = new IXR_Date(trim($this->_currentTagContents));
208                // $value = $iso->getTimestamp();
209                $this->_currentTagContents = '';
210                $valueFlag = true;
211                break;
212            case 'value':
213                // "If no type is indicated, the type is string."
214                if (trim($this->_currentTagContents) != '') {
215                    $value = (string)$this->_currentTagContents;
216                    $this->_currentTagContents = '';
217                    $valueFlag = true;
218                }
219                break;
220            case 'boolean':
221                $value = (boolean)trim($this->_currentTagContents);
222                $this->_currentTagContents = '';
223                $valueFlag = true;
224                break;
225            case 'base64':
226                $value = base64_decode($this->_currentTagContents);
227                $this->_currentTagContents = '';
228                $valueFlag = true;
229                break;
230            /* Deal with stacks of arrays and structs */
231            case 'data':
232            case 'struct':
233                $value = array_pop($this->_arraystructs);
234                array_pop($this->_arraystructstypes);
235                $valueFlag = true;
236                break;
237            case 'member':
238                array_pop($this->_currentStructName);
239                break;
240            case 'name':
241                $this->_currentStructName[] = trim($this->_currentTagContents);
242                $this->_currentTagContents = '';
243                break;
244            case 'methodName':
245                $this->methodName = trim($this->_currentTagContents);
246                $this->_currentTagContents = '';
247                break;
248        }
249        if ($valueFlag) {
250            /*
251            if (!is_array($value) && !is_object($value)) {
252                $value = trim($value);
253            }
254            */
255            if (count($this->_arraystructs) > 0) {
256                // Add value to struct or array
257                if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {
258                    // Add to struct
259                    $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value;
260                } else {
261                    // Add to array
262                    $this->_arraystructs[count($this->_arraystructs)-1][] = $value;
263                }
264            } else {
265                // Just add as a paramater
266                $this->params[] = $value;
267            }
268        }
269    }       
270}
271
272
273class IXR_Server {
274    var $data;
275    var $callbacks = array();
276    var $message;
277    var $capabilities;
278    function IXR_Server($callbacks = false, $data = false) {
279        $this->setCapabilities();
280        if ($callbacks) {
281            $this->callbacks = $callbacks;
282        }
283        $this->setCallbacks();
284        $this->serve($data);
285    }
286    function serve($data = false) {
287        if (!$data) {
288            global $HTTP_RAW_POST_DATA;
289            if (!$HTTP_RAW_POST_DATA) {
290               die('XML-RPC server accepts POST requests only.');
291            }
292            $data = $HTTP_RAW_POST_DATA;
293        }
294        $this->message = new IXR_Message($data);
295        if (!$this->message->parse()) {
296            $this->error(-32700, 'parse error. not well formed');
297        }
298        if ($this->message->messageType != 'methodCall') {
299            $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall');
300        }
301        $result = $this->call($this->message->methodName, $this->message->params);
302        // Is the result an error?
303        if (is_a($result, 'IXR_Error')) {
304            $this->error($result);
305        }
306        // Encode the result
307        $r = new IXR_Value($result);
308        $resultxml = $r->getXml();
309        // Create the XML
310        $xml = <<<EOD
311<methodResponse>
312  <params>
313    <param>
314      <value>
315        $resultxml
316      </value>
317    </param>
318  </params>
319</methodResponse>
320
321EOD;
322        // Send it
323        $this->output($xml);
324    }
325    function call($methodname, $args) {
326        if (!$this->hasMethod($methodname)) {
327            return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.');
328        }
329        $method = $this->callbacks[$methodname];
330        // Perform the callback and send the response
331        if (count($args) == 1) {
332            // If only one paramater just send that instead of the whole array
333            $args = $args[0];
334        }
335        // Are we dealing with a function or a method?
336        if (substr($method, 0, 5) == 'this:') {
337            // It's a class method - check it exists
338            $method = substr($method, 5);
339            if (!method_exists($this, $method)) {
340                return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.');
341            }
342            // Call the method
343            $result = $this->$method($args);
344        } else {
345            // It's a function - does it exist?
346            if (!function_exists($method)) {
347                return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.');
348            }
349            // Call the function
350            $result = $method($args);
351        }
352        return $result;
353    }
354
355    function error($error, $message = false) {
356        // Accepts either an error object or an error code and message
357        if ($message && !is_object($error)) {
358            $error = new IXR_Error($error, $message);
359        }
360        $this->output($error->getXml());
361    }
362    function output($xml) {
363        $xml = '<?xml version="1.0"?>'."\n".$xml;
364        $length = strlen($xml);
365        header('Connection: close');
366        header('Content-Length: '.$length);
367        header('Content-Type: text/xml');
368        header('Date: '.date('r'));
369        echo $xml;
370        exit;
371    }
372    function hasMethod($method) {
373        return in_array($method, array_keys($this->callbacks));
374    }
375    function setCapabilities() {
376        // Initialises capabilities array
377        $this->capabilities = array(
378            'xmlrpc' => array(
379                'specUrl' => 'http://www.xmlrpc.com/spec',
380                'specVersion' => 1
381            ),
382            'faults_interop' => array(
383                'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
384                'specVersion' => 20010516
385            ),
386            'system.multicall' => array(
387                'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
388                'specVersion' => 1
389            ),
390        );   
391    }
392    function getCapabilities($args) {
393        return $this->capabilities;
394    }
395    function setCallbacks() {
396        $this->callbacks['system.getCapabilities'] = 'this:getCapabilities';
397        $this->callbacks['system.listMethods'] = 'this:listMethods';
398        $this->callbacks['system.multicall'] = 'this:multiCall';
399    }
400    function listMethods($args) {
401        // Returns a list of methods - uses array_reverse to ensure user defined
402        // methods are listed before server defined methods
403        return array_reverse(array_keys($this->callbacks));
404    }
405    function multiCall($methodcalls) {
406        // See http://www.xmlrpc.com/discuss/msgReader$1208
407        $return = array();
408        foreach ($methodcalls as $call) {
409            $method = $call['methodName'];
410            $params = $call['params'];
411            if ($method == 'system.multicall') {
412                $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden');
413            } else {
414                $result = $this->call($method, $params);
415            }
416            if (is_a($result, 'IXR_Error')) {
417                $return[] = array(
418                    'faultCode' => $result->code,
419                    'faultString' => $result->message
420                );
421            } else {
422                $return[] = array($result);
423            }
424        }
425        return $return;
426    }
427}
428
429class IXR_Request {
430    var $method;
431    var $args;
432    var $xml;
433    function IXR_Request($method, $args) {
434        $this->method = $method;
435        $this->args = $args;
436        $this->xml = <<<EOD
437<?xml version="1.0"?>
438<methodCall>
439<methodName>{$this->method}</methodName>
440<params>
441
442EOD;
443        foreach ($this->args as $arg) {
444            $this->xml .= '<param><value>';
445            $v = new IXR_Value($arg);
446            $this->xml .= $v->getXml();
447            $this->xml .= "</value></param>\n";
448        }
449        $this->xml .= '</params></methodCall>';
450    }
451    function getLength() {
452        return strlen($this->xml);
453    }
454    function getXml() {
455        return $this->xml;
456    }
457}
458
459
460class IXR_Client {
461    var $server;
462    var $port;
463    var $path;
464    var $useragent;
465    var $response;
466    var $message = false;
467    var $debug = false;
468    // Storage place for an error message
469    var $error = false;
470    function IXR_Client($server, $path = false, $port = 80) {
471        if (!$path) {
472            // Assume we have been given a URL instead
473            $bits = parse_url($server);
474            $this->server = $bits['host'];
475            $this->port = isset($bits['port']) ? $bits['port'] : 80;
476            $this->path = isset($bits['path']) ? $bits['path'] : '/';
477            // Make absolutely sure we have a path
478            if (!$this->path) {
479                $this->path = '/';
480            }
481        } else {
482            $this->server = $server;
483            $this->path = $path;
484            $this->port = $port;
485        }
486        $this->useragent = 'The Incutio XML-RPC PHP Library';
487    }
488    function query() {
489        $args = func_get_args();
490        $method = array_shift($args);
491        $request = new IXR_Request($method, $args);
492        $length = $request->getLength();
493        $xml = $request->getXml();
494        $r = "\r\n";
495        $request  = "POST {$this->path} HTTP/1.0$r";
496        $request .= "Host: {$this->server}$r";
497        $request .= "Content-Type: text/xml$r";
498        $request .= "User-Agent: {$this->useragent}$r";
499        $request .= "Content-length: {$length}$r$r";
500        $request .= $xml;
501        // Now send the request
502        if ($this->debug) {
503            echo '<pre>'.htmlspecialchars($request)."\n</pre>\n\n";
504        }
505        $fp = @fsockopen($this->server, $this->port);
506        if (!$fp) {
507            $this->error = new IXR_Error(-32300, 'transport error - could not open socket');
508            return false;
509        }
510        fputs($fp, $request);
511        $contents = '';
512        $gotFirstLine = false;
513        $gettingHeaders = true;
514        while (!feof($fp)) {
515            $line = fgets($fp, 4096);
516            if (!$gotFirstLine) {
517                // Check line for '200'
518                if (strstr($line, '200') === false) {
519                    $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200');
520                    return false;
521                }
522                $gotFirstLine = true;
523            }
524            if (trim($line) == '') {
525                $gettingHeaders = false;
526            }
527            if (!$gettingHeaders) {
528                $contents .= trim($line)."\n";
529            }
530        }
531        if ($this->debug) {
532            echo '<pre>'.htmlspecialchars($contents)."\n</pre>\n\n";
533        }
534        // Now parse what we've got back
535        $this->message = new IXR_Message($contents);
536        if (!$this->message->parse()) {
537            // XML error
538            $this->error = new IXR_Error(-32700, 'parse error. not well formed');
539            return false;
540        }
541        // Is the message a fault?
542        if ($this->message->messageType == 'fault') {
543            $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);
544            return false;
545        }
546        // Message must be OK
547        return true;
548    }
549    function getResponse() {
550        // methodResponses can only have one param - return that
551        return $this->message->params[0];
552    }
553    function isError() {
554        return (is_object($this->error));
555    }
556    function getErrorCode() {
557        return $this->error->code;
558    }
559    function getErrorMessage() {
560        return $this->error->message;
561    }
562}
563
564
565class IXR_Error {
566    var $code;
567    var $message;
568    function IXR_Error($code, $message) {
569        $this->code = $code;
570        $this->message = $message;
571    }
572    function getXml() {
573        $xml = <<<EOD
574<methodResponse>
575  <fault>
576    <value>
577      <struct>
578        <member>
579          <name>faultCode</name>
580          <value><int>{$this->code}</int></value>
581        </member>
582        <member>
583          <name>faultString</name>
584          <value><string>{$this->message}</string></value>
585        </member>
586      </struct>
587    </value>
588  </fault>
589</methodResponse>
590
591EOD;
592        return $xml;
593    }
594}
595
596
597class IXR_Date {
598    var $year;
599    var $month;
600    var $day;
601    var $hour;
602    var $minute;
603    var $second;
604    function IXR_Date($time