HomeHelpTrac

source: trunk/xmlrpc/lib/xmlrpcs.inc @ 8596

Revision 8596, 37.8 KB checked in by Alexander Trofimov, 3 years ago (diff)
Line 
1<?php
2// by Edd Dumbill (C) 1999-2002
3// <edd@usefulinc.com>
4// $Id: xmlrpcs.inc,v 1.69 2007/09/20 20:14:25 ggiunta Exp $
5
6// Copyright (c) 1999,2000,2002 Edd Dumbill.
7// All rights reserved.
8//
9// Redistribution and use in source and binary forms, with or without
10// modification, are permitted provided that the following conditions
11// are met:
12//
13//    * Redistributions of source code must retain the above copyright
14//      notice, this list of conditions and the following disclaimer.
15//
16//    * Redistributions in binary form must reproduce the above
17//      copyright notice, this list of conditions and the following
18//      disclaimer in the documentation and/or other materials provided
19//      with the distribution.
20//
21//    * Neither the name of the "XML-RPC for PHP" nor the names of its
22//      contributors may be used to endorse or promote products derived
23//      from this software without specific prior written permission.
24//
25// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
28// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
29// REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
30// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
36// OF THE POSSIBILITY OF SUCH DAMAGE.
37
38    // XML RPC Server class
39    // requires: xmlrpc.inc
40
41    $GLOBALS['xmlrpcs_capabilities'] = array(
42        // xmlrpc spec: always supported
43        'xmlrpc' => new xmlrpcval(array(
44            'specUrl' => new xmlrpcval('http://www.xmlrpc.com/spec', 'string'),
45            'specVersion' => new xmlrpcval(1, 'int')
46        ), 'struct'),
47        // if we support system.xxx functions, we always support multicall, too...
48        // Note that, as of 2006/09/17, the following URL does not respond anymore
49        'system.multicall' => new xmlrpcval(array(
50            'specUrl' => new xmlrpcval('http://www.xmlrpc.com/discuss/msgReader$1208', 'string'),
51            'specVersion' => new xmlrpcval(1, 'int')
52        ), 'struct'),
53        // introspection: version 2! we support 'mixed', too
54        'introspection' => new xmlrpcval(array(
55            'specUrl' => new xmlrpcval('http://phpxmlrpc.sourceforge.net/doc-2/ch10.html', 'string'),
56            'specVersion' => new xmlrpcval(2, 'int')
57        ), 'struct')
58    );
59
60    /* Functions that implement system.XXX methods of xmlrpc servers */
61    $_xmlrpcs_getCapabilities_sig=array(array($GLOBALS['xmlrpcStruct']));
62    $_xmlrpcs_getCapabilities_doc='This method lists all the capabilites that the XML-RPC server has: the (more or less standard) extensions to the xmlrpc spec that it adheres to';
63    $_xmlrpcs_getCapabilities_sdoc=array(array('list of capabilities, described as structs with a version number and url for the spec'));
64    function _xmlrpcs_getCapabilities($server, $m=null)
65    {
66        $outAr = $GLOBALS['xmlrpcs_capabilities'];
67        // NIL extension
68        if ($GLOBALS['xmlrpc_null_extension']) {
69            $outAr['nil'] = new xmlrpcval(array(
70                'specUrl' => new xmlrpcval('http://www.ontosys.com/xml-rpc/extensions.php', 'string'),
71                'specVersion' => new xmlrpcval(1, 'int')
72            ), 'struct');
73        }
74        return new xmlrpcresp(new xmlrpcval($outAr, 'struct'));
75    }
76
77    // listMethods: signature was either a string, or nothing.
78    // The useless string variant has been removed
79    $_xmlrpcs_listMethods_sig=array(array($GLOBALS['xmlrpcArray']));
80    $_xmlrpcs_listMethods_doc='This method lists all the methods that the XML-RPC server knows how to dispatch';
81    $_xmlrpcs_listMethods_sdoc=array(array('list of method names'));
82    function _xmlrpcs_listMethods($server, $m=null) // if called in plain php values mode, second param is missing
83    {
84
85        $outAr=array();
86        foreach($server->dmap as $key => $val)
87        {
88            $outAr[]=&new xmlrpcval($key, 'string');
89        }
90        if($server->allow_system_funcs)
91        {
92            foreach($GLOBALS['_xmlrpcs_dmap'] as $key => $val)
93            {
94                $outAr[]=&new xmlrpcval($key, 'string');
95            }
96        }
97        return new xmlrpcresp(new xmlrpcval($outAr, 'array'));
98    }
99
100    $_xmlrpcs_methodSignature_sig=array(array($GLOBALS['xmlrpcArray'], $GLOBALS['xmlrpcString']));
101    $_xmlrpcs_methodSignature_doc='Returns an array of known signatures (an array of arrays) for the method name passed. If no signatures are known, returns a none-array (test for type != array to detect missing signature)';
102    $_xmlrpcs_methodSignature_sdoc=array(array('list of known signatures, each sig being an array of xmlrpc type names', 'name of method to be described'));
103    function _xmlrpcs_methodSignature($server, $m)
104    {
105        // let accept as parameter both an xmlrpcval or string
106        if (is_object($m))
107        {
108            $methName=$m->getParam(0);
109            $methName=$methName->scalarval();
110        }
111        else
112        {
113            $methName=$m;
114        }
115        if(strpos($methName, "system.") === 0)
116        {
117            $dmap=$GLOBALS['_xmlrpcs_dmap']; $sysCall=1;
118        }
119        else
120        {
121            $dmap=$server->dmap; $sysCall=0;
122        }
123        if(isset($dmap[$methName]))
124        {
125            if(isset($dmap[$methName]['signature']))
126            {
127                $sigs=array();
128                foreach($dmap[$methName]['signature'] as $inSig)
129                {
130                    $cursig=array();
131                    foreach($inSig as $sig)
132                    {
133                        $cursig[]=&new xmlrpcval($sig, 'string');
134                    }
135                    $sigs[]=&new xmlrpcval($cursig, 'array');
136                }
137                $r=&new xmlrpcresp(new xmlrpcval($sigs, 'array'));
138            }
139            else
140            {
141                // NB: according to the official docs, we should be returning a
142                // "none-array" here, which means not-an-array
143                $r=&new xmlrpcresp(new xmlrpcval('undef', 'string'));
144            }
145        }
146        else
147        {
148            $r=&new xmlrpcresp(0,$GLOBALS['xmlrpcerr']['introspect_unknown'], $GLOBALS['xmlrpcstr']['introspect_unknown']);
149        }
150        return $r;
151    }
152
153    $_xmlrpcs_methodHelp_sig=array(array($GLOBALS['xmlrpcString'], $GLOBALS['xmlrpcString']));
154    $_xmlrpcs_methodHelp_doc='Returns help text if defined for the method passed, otherwise returns an empty string';
155    $_xmlrpcs_methodHelp_sdoc=array(array('method description', 'name of the method to be described'));
156    function _xmlrpcs_methodHelp($server, $m)
157    {
158        // let accept as parameter both an xmlrpcval or string
159        if (is_object($m))
160        {
161            $methName=$m->getParam(0);
162            $methName=$methName->scalarval();
163        }
164        else
165        {
166            $methName=$m;
167        }
168        if(strpos($methName, "system.") === 0)
169        {
170            $dmap=$GLOBALS['_xmlrpcs_dmap']; $sysCall=1;
171        }
172        else
173        {
174            $dmap=$server->dmap; $sysCall=0;
175        }
176        if(isset($dmap[$methName]))
177        {
178            if(isset($dmap[$methName]['docstring']))
179            {
180                $r=&new xmlrpcresp(new xmlrpcval($dmap[$methName]['docstring']), 'string');
181            }
182            else
183            {
184                $r=&new xmlrpcresp(new xmlrpcval('', 'string'));
185            }
186        }
187        else
188        {
189            $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['introspect_unknown'], $GLOBALS['xmlrpcstr']['introspect_unknown']);
190        }
191        return $r;
192    }
193
194    $_xmlrpcs_multicall_sig = array(array($GLOBALS['xmlrpcArray'], $GLOBALS['xmlrpcArray']));
195    $_xmlrpcs_multicall_doc = 'Boxcar multiple RPC calls in one request. See http://www.xmlrpc.com/discuss/msgReader$1208 for details';
196    $_xmlrpcs_multicall_sdoc = array(array('list of response structs, where each struct has the usual members', 'list of calls, with each call being represented as a struct, with members "methodname" and "params"'));
197    function _xmlrpcs_multicall_error($err)
198    {
199        if(is_string($err))
200        {
201            $str = $GLOBALS['xmlrpcstr']["multicall_${err}"];
202            $code = $GLOBALS['xmlrpcerr']["multicall_${err}"];
203        }
204        else
205        {
206            $code = $err->faultCode();
207            $str = $err->faultString();
208        }
209        $struct = array();
210        $struct['faultCode'] =& new xmlrpcval($code, 'int');
211        $struct['faultString'] =& new xmlrpcval($str, 'string');
212        return new xmlrpcval($struct, 'struct');
213    }
214
215    function _xmlrpcs_multicall_do_call($server, $call)
216    {
217        if($call->kindOf() != 'struct')
218        {
219            return _xmlrpcs_multicall_error('notstruct');
220        }
221        $methName = @$call->structmem('methodName');
222        if(!$methName)
223        {
224            return _xmlrpcs_multicall_error('nomethod');
225        }
226        if($methName->kindOf() != 'scalar' || $methName->scalartyp() != 'string')
227        {
228            return _xmlrpcs_multicall_error('notstring');
229        }
230        if($methName->scalarval() == 'system.multicall')
231        {
232            return _xmlrpcs_multicall_error('recursion');
233        }
234
235        $params = @$call->structmem('params');
236        if(!$params)
237        {
238            return _xmlrpcs_multicall_error('noparams');
239        }
240        if($params->kindOf() != 'array')
241        {
242            return _xmlrpcs_multicall_error('notarray');
243        }
244        $numParams = $params->arraysize();
245
246        $msg =& new xmlrpcmsg($methName->scalarval());
247        for($i = 0; $i < $numParams; $i++)
248        {
249            if(!$msg->addParam($params->arraymem($i)))
250            {
251                $i++;
252                return _xmlrpcs_multicall_error(new xmlrpcresp(0,
253                    $GLOBALS['xmlrpcerr']['incorrect_params'],
254                    $GLOBALS['xmlrpcstr']['incorrect_params'] . ": probable xml error in param " . $i));
255            }
256        }
257
258        $result = $server->execute($msg);
259
260        if($result->faultCode() != 0)
261        {
262            return _xmlrpcs_multicall_error($result);       // Method returned fault.
263        }
264
265        return new xmlrpcval(array($result->value()), 'array');
266    }
267
268    function _xmlrpcs_multicall_do_call_phpvals($server, $call)
269    {
270        if(!is_array($call))
271        {
272            return _xmlrpcs_multicall_error('notstruct');
273        }
274        if(!array_key_exists('methodName', $call))
275        {
276            return _xmlrpcs_multicall_error('nomethod');
277        }
278        if (!is_string($call['methodName']))
279        {
280            return _xmlrpcs_multicall_error('notstring');
281        }
282        if($call['methodName'] == 'system.multicall')
283        {
284            return _xmlrpcs_multicall_error('recursion');
285        }
286        if(!array_key_exists('params', $call))
287        {
288            return _xmlrpcs_multicall_error('noparams');
289        }
290        if(!is_array($call['params']))
291        {
292            return _xmlrpcs_multicall_error('notarray');
293        }
294
295        // this is a real dirty and simplistic hack, since we might have received a
296        // base64 or datetime values, but they will be listed as strings here...
297        $numParams = count($call['params']);
298        $pt = array();
299        foreach($call['params'] as $val)
300            $pt[] = php_2_xmlrpc_type(gettype($val));
301
302        $result = $server->execute($call['methodName'], $call['params'], $pt);
303
304        if($result->faultCode() != 0)
305        {
306            return _xmlrpcs_multicall_error($result);       // Method returned fault.
307        }
308
309        return new xmlrpcval(array($result->value()), 'array');
310    }
311
312    function _xmlrpcs_multicall($server, $m)
313    {
314        $result = array();
315        // let accept a plain list of php parameters, beside a single xmlrpc msg object
316        if (is_object($m))
317        {
318            $calls = $m->getParam(0);
319            $numCalls = $calls->arraysize();
320            for($i = 0; $i < $numCalls; $i++)
321            {
322                $call = $calls->arraymem($i);
323                $result[$i] = _xmlrpcs_multicall_do_call($server, $call);
324            }
325        }
326        else
327        {
328            $numCalls=count($m);
329            for($i = 0; $i < $numCalls; $i++)
330            {
331                $result[$i] = _xmlrpcs_multicall_do_call_phpvals($server, $m[$i]);
332            }
333        }
334
335        return new xmlrpcresp(new xmlrpcval($result, 'array'));
336    }
337
338    $GLOBALS['_xmlrpcs_dmap']=array(
339        'system.listMethods' => array(
340            'function' => '_xmlrpcs_listMethods',
341            'signature' => $_xmlrpcs_listMethods_sig,
342            'docstring' => $_xmlrpcs_listMethods_doc,
343            'signature_docs' => $_xmlrpcs_listMethods_sdoc),
344        'system.methodHelp' => array(
345            'function' => '_xmlrpcs_methodHelp',
346            'signature' => $_xmlrpcs_methodHelp_sig,
347            'docstring' => $_xmlrpcs_methodHelp_doc,
348            'signature_docs' => $_xmlrpcs_methodHelp_sdoc),
349        'system.methodSignature' => array(
350            'function' => '_xmlrpcs_methodSignature',
351            'signature' => $_xmlrpcs_methodSignature_sig,
352            'docstring' => $_xmlrpcs_methodSignature_doc,
353            'signature_docs' => $_xmlrpcs_methodSignature_sdoc),
354        'system.multicall' => array(
355            'function' => '_xmlrpcs_multicall',
356            'signature' => $_xmlrpcs_multicall_sig,
357            'docstring' => $_xmlrpcs_multicall_doc,
358            'signature_docs' => $_xmlrpcs_multicall_sdoc),
359        'system.getCapabilities' => array(
360            'function' => '_xmlrpcs_getCapabilities',
361            'signature' => $_xmlrpcs_getCapabilities_sig,
362            'docstring' => $_xmlrpcs_getCapabilities_doc,
363            'signature_docs' => $_xmlrpcs_getCapabilities_sdoc)
364    );
365
366    $GLOBALS['_xmlrpcs_occurred_errors'] = '';
367    $GLOBALS['_xmlrpcs_prev_ehandler'] = '';
368    /**
369    * Error handler used to track errors that occur during server-side execution of PHP code.
370    * This allows to report back to the client whether an internal error has occurred or not
371    * using an xmlrpc response object, instead of letting the client deal with the html junk
372    * that a PHP execution error on the server generally entails.
373    *
374    * NB: in fact a user defined error handler can only handle WARNING, NOTICE and USER_* errors.
375    *
376    */
377    function _xmlrpcs_errorHandler($errcode, $errstring, $filename=null, $lineno=null, $context=null)
378    {
379        // obey the @ protocol
380        if (error_reporting() == 0)
381            return;
382
383        //if($errcode != E_NOTICE && $errcode != E_WARNING && $errcode != E_USER_NOTICE && $errcode != E_USER_WARNING)
384        if($errcode != 2048) // do not use E_STRICT by name, since on PHP 4 it will not be defined
385        {
386            $GLOBALS['_xmlrpcs_occurred_errors'] = $GLOBALS['_xmlrpcs_occurred_errors'] . $errstring . "\n";
387        }
388        // Try to avoid as much as possible disruption to the previous error handling
389        // mechanism in place
390        if($GLOBALS['_xmlrpcs_prev_ehandler'] == '')
391        {
392            // The previous error handler was the default: all we should do is log error
393            // to the default error log (if level high enough)
394            if(ini_get('log_errors') && (intval(ini_get('error_reporting')) & $errcode))
395            {
396                error_log($errstring);
397            }
398        }
399        else
400        {
401            // Pass control on to previous error handler, trying to avoid loops...
402            if($GLOBALS['_xmlrpcs_prev_ehandler'] != '_xmlrpcs_errorHandler')
403            {
404                // NB: this code will NOT work on php < 4.0.2: only 2 params were used for error handlers
405                if(is_array($GLOBALS['_xmlrpcs_prev_ehandler']))
406                {
407                    // the following works both with static class methods and plain object methods as error handler
408                    call_user_func_array($GLOBALS['_xmlrpcs_prev_ehandler'], array($errcode, $errstring, $filename, $lineno, $context));
409                }
410                else
411                {
412                    $GLOBALS['_xmlrpcs_prev_ehandler']($errcode, $errstring, $filename, $lineno, $context);
413                }
414            }
415        }
416    }
417
418    $GLOBALS['_xmlrpc_debuginfo']='';
419
420    /**
421    * Add a string to the debug info that can be later seralized by the server
422    * as part of the response message.
423    * Note that for best compatbility, the debug string should be encoded using
424    * the $GLOBALS['xmlrpc_internalencoding'] character set.
425    * @param string $m
426    * @access public
427    */
428    function xmlrpc_debugmsg($m)
429    {
430        $GLOBALS['_xmlrpc_debuginfo'] .= $m . "\n";
431    }
432
433    class xmlrpc_server
434    {
435        /// array defining php functions exposed as xmlrpc methods by this server
436        var $dmap=array();
437        /**
438        * Defines how functions in dmap will be invokde: either using an xmlrpc msg object
439        * or plain php values.
440        * valid strings are 'xmlrpcvals', 'phpvals' or 'epivals'
441        */
442        var $functions_parameters_type='xmlrpcvals';
443        /// controls wether the server is going to echo debugging messages back to the client as comments in response body. valid values: 0,1,2,3
444        var $debug = 1;
445        /**
446        * When set to true, it will enable HTTP compression of the response, in case
447        * the client has declared its support for compression in the request.
448        */
449        var $compress_response = false;
450        /**
451        * List of http compression methods accepted by the server for requests.
452        * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib
453        */
454        var $accepted_compression = array();
455        /// shall we serve calls to system.* methods?
456        var $allow_system_funcs = true;
457        /// list of charset encodings natively accepted for requests
458        var $accepted_charset_encodings = array();
459        /**
460        * charset encoding to be used for response.
461        * NB: if we can, we will convert the generated response from internal_encoding to the intended one.
462        * can be: a supported xml encoding (only UTF-8 and ISO-8859-1 at present, unless mbstring is enabled),
463        * null (leave unspecified in response, convert output stream to US_ASCII),
464        * 'default' (use xmlrpc library default as specified in xmlrpc.inc, convert output stream if needed),
465        * or 'auto' (use client-specified charset encoding or same as request if request headers do not specify it (unless request is US-ASCII: then use library default anyway).
466        * NB: pretty dangerous if you accept every charset and do not have mbstring enabled)
467        */
468        var $response_charset_encoding = '';
469        /// storage for internal debug info
470        var $debug_info = '';
471        /// extra data passed at runtime to method handling functions. Used only by EPI layer
472        var $user_data = null;
473
474        /**
475        * @param array $dispmap the dispatch map withd efinition of exposed services
476        * @param boolean $servicenow set to false to prevent the server from runnung upon construction
477        */
478        function xmlrpc_server($dispMap=null, $serviceNow=true)
479        {
480            // if ZLIB is enabled, let the server by default accept compressed requests,
481            // and compress responses sent to clients that support them
482            if(function_exists('gzinflate'))
483            {
484                $this->accepted_compression = array(); //array('gzip', 'deflate'); !!!
485                $this->compress_response = true;
486            }
487
488            // by default the xml parser can support these 3 charset encodings
489            $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII');
490
491            // dispMap is a dispatch array of methods
492            // mapped to function names and signatures
493            // if a method
494            // doesn't appear in the map then an unknown
495            // method error is generated
496            /* milosch - changed to make passing dispMap optional.
497             * instead, you can use the class add_to_map() function
498             * to add functions manually (borrowed from SOAPX4)
499             */
500            if($dispMap)
501            {
502                $this->dmap = $dispMap;
503                if($serviceNow)
504                {
505                    $this->service();
506                }
507            }
508        }
509
510        /**
511        * Set debug level of server.
512        * @param integer $in debug lvl: determines info added to xmlrpc responses (as xml comments)
513        * 0 = no debug info,
514        * 1 = msgs set from user with debugmsg(),
515        * 2 = add complete xmlrpc request (headers and body),
516        * 3 = add also all processing warnings happened during method processing
517        * (NB: this involves setting a custom error handler, and might interfere
518        * with the standard processing of the php function exposed as method. In
519        * particular, triggering an USER_ERROR level error will not halt script
520        * execution anymore, but just end up logged in the xmlrpc response)
521        * Note that info added at elevel 2 and 3 will be base64 encoded
522        * @access public
523        */
524        function setDebug($in)
525        {
526            $this->debug=$in;
527        }
528
529        /**
530        * Return a string with the serialized representation of all debug info
531        * @param string $charset_encoding the target charset encoding for the serialization
532        * @return string an XML comment (or two)
533        */
534        function serializeDebug($charset_encoding='')
535        {
536            // Tough encoding problem: which internal charset should we assume for debug info?
537            // It might contain a copy of raw data received from client, ie with unknown encoding,
538            // intermixed with php generated data and user generated data...
539            // so we split it: system debug is base 64 encoded,
540            // user debug info should be encoded by the end user using the INTERNAL_ENCODING
541            $out = '';
542            if ($this->debug_info != '')
543            {
544                $out .= "<!-- SERVER DEBUG INFO (BASE64 ENCODED):\n".base64_encode($this->debug_info)."\n-->\n";
545            }
546            if($GLOBALS['_xmlrpc_debuginfo']!='')
547            {
548
549                $out .= "<!-- DEBUG INFO:\n" . xmlrpc_encode_entitites(str_replace('--', '_-', $GLOBALS['_xmlrpc_debuginfo']), $GLOBALS['xmlrpc_internalencoding'], $charset_encoding) . "\n-->\n";
550                // NB: a better solution MIGHT be to use CDATA, but we need to insert it
551                // into return payload AFTER the beginning tag
552                //$out .= "<![CDATA[ DEBUG INFO:\n\n" . str_replace(']]>', ']_]_>', $GLOBALS['_xmlrpc_debuginfo']) . "\n]]>\n";
553            }
554            return $out;
555        }
556
557        /**
558        * Execute the xmlrpc request, printing the response
559        * @param string $data the request body. If null, the http POST request will be examined
560        * @return xmlrpcresp the response object (usually not used by caller...)
561        * @access public
562        */
563        function service($data=null, $return_payload=false)
564        {
565            if ($data === null)
566            {
567                // workaround for a known bug in php ver. 5.2.2 that broke $HTTP_RAW_POST_DATA
568                $ver = phpversion();
569                if ($ver[0] >= 5)
570                {
571                    $data = file_get_contents('php://input');
572                }
573                else
574                {
575                    $data = isset($GLOBALS['HTTP_RAW_POST_DATA']) ? $GLOBALS['HTTP_RAW_POST_DATA'] : '';
576                }
577            }
578            $raw_data = $data;
579
580            // reset internal debug info
581            $this->debug_info = '';
582
583            // Echo back what we received, before parsing it
584            if($this->debug > 1)
585            {
586                $this->debugmsg("+++GOT+++\n" . $data . "\n+++END+++");
587            }
588
589            $r = $this->parseRequestHeaders($data, $req_charset, $resp_charset, $resp_encoding);
590            if (!$r)
591            {
592                $r=$this->parseRequest($data, $req_charset);
593            }
594
595            // save full body of request into response, for more debugging usages
596            $r->raw_data = $raw_data;
597
598            if($this->debug > 2 && $GLOBALS['_xmlrpcs_occurred_errors'])
599            {
600                $this->debugmsg("+++PROCESSING ERRORS AND WARNINGS+++\n" .
601                    $GLOBALS['_xmlrpcs_occurred_errors'] . "+++END+++");
602            }
603
604            $payload=$this->xml_header($resp_charset);
605            if($this->debug > 0)
606            {
607                $payload = $payload . $this->serializeDebug($resp_charset);
608            }
609
610            // G. Giunta 2006-01-27: do not create response serialization if it has
611            // already happened. Helps building json magic
612            if (empty($r->payload))
613            {
614                $r->serialize($resp_charset);
615            }
616            $payload = $payload . $r->payload;
617
618            if ($return_payload)
619            {
620                return $payload;
621            }
622
623            // if we get a warning/error that has output some text before here, then we cannot
624            // add a new header. We cannot say we are sending xml, either...
625            if(!headers_sent())
626            {
627                header('Content-Type: '.$r->content_type);
628                // we do not know if client actually told us an accepted charset, but if he did
629                // we have to tell him what we did
630                header("Vary: Accept-Charset");
631
632                // http compression of output: only
633                // if we can do it, and we want to do it, and client asked us to,
634                // and php ini settings do not force it already
635                $php_no_self_compress = ini_get('zlib.output_compression') == '' && (ini_get('output_handler') != 'ob_gzhandler');
636                if($this->compress_response && function_exists('gzencode') && $resp_encoding != ''
637                    && $php_no_self_compress)
638                {
639                    if(strpos($resp_encoding, 'gzip') !== false)
640                    {
641                        $payload = gzencode($payload);
642                        header("Content-Encoding: gzip");
643                        header("Vary: Accept-Encoding");
644                    }
645                    elseif (strpos($resp_encoding, 'deflate') !== false)
646                    {
647                        $payload = gzcompress($payload);
648                        header("Content-Encoding: deflate");
649                        header("Vary: Accept-Encoding");
650                    }
651                }
652
653                // do not ouput content-length header if php is compressing output for us:
654                // it will mess up measurements
655                if($php_no_self_compress)
656                {
657                    header('Content-Length: ' . (int)strlen($payload));
658                }
659            }
660            else
661            {
662                error_log('XML-RPC: xmlrpc_server::service: http headers already sent before response is fully generated. Check for php warning or error messages');
663            }
664
665            print $payload;
666
667            // return request, in case subclasses want it
668            return $r;
669        }
670
671        /**
672        * Add a method to the dispatch map
673        * @param string $methodname the name with which the method will be made available
674        * @param string $function the php function that will get invoked
675        * @param array $sig the array of valid method signatures
676        * @param string $doc method documentation
677        * @access public
678        */
679        function add_to_map($methodname,$function,$sig=null,$doc='')
680        {
681            $this->dmap[$methodname] = array(
682                'function'  => $function,
683                'docstring' => $doc
684            );
685            if ($sig)
686            {
687                $this->dmap[$methodname]['signature'] = $sig;
688            }
689        }
690
691        /**
692        * Verify type and number of parameters received against a list of known signatures
693        * @param array $in array of either xmlrpcval objects or xmlrpc type definitions
694        * @param array $sig array of known signatures to match against
695        * @access private
696        */
697        function verifySignature($in, $sig)
698        {
699            // check each possible signature in turn
700            if (is_object($in))
701            {
702                $numParams = $in->getNumParams();
703            }
704            else
705            {
706                $numParams = count($in);
707            }
708            foreach($sig as $cursig)
709            {
710                if(count($cursig)==$numParams+1)
711                {
712                    $itsOK=1;
713                    for($n=0; $n<$numParams; $n++)
714                    {
715                        if (is_object($in))
716                        {
717                            $p=$in->getParam($n);
718                            if($p->kindOf() == 'scalar')
719                            {
720                                $pt=$p->scalartyp();
721                            }
722                            else
723                            {
724                                $pt=$p->kindOf();
725                            }
726                        }
727                        else
728                        {
729                            $pt= $in[$n] == 'i4' ? 'int' : $in[$n]; // dispatch maps never use i4...
730                        }
731
732                        // param index is $n+1, as first member of sig is return type
733                        if($pt != $cursig[$n+1] && $cursig[$n+1] != $GLOBALS['xmlrpcValue'])
734                        {
735                            $itsOK=0;
736                            $pno=$n+1;
737                            $wanted=$cursig[$n+1];
738                            $got=$pt;
739                            break;
740                        }
741                    }
742                    if($itsOK)
743                    {
744                        return array(1,'');
745                    }
746                }
747            }
748            if(isset($wanted))
749            {
750                return array(0, "Wanted ${wanted}, got ${got} at param ${pno}");
751            }
752            else
753            {
754                return array(0, "No method signature matches number of parameters");
755            }
756        }
757
758        /**
759        * Parse http headers received along with xmlrpc request. If needed, inflate request
760        * @return null on success or an xmlrpcresp
761        * @access private
762        */
763        function parseRequestHeaders(&$data, &$req_encoding, &$resp_encoding, &$resp_compression)
764        {
765            // Play nice to PHP 4.0.x: superglobals were not yet invented...
766            if(!isset($_SERVER))
767            {
768                $_SERVER = $GLOBALS['HTTP_SERVER_VARS'];
769            }
770
771            if($this->debug > 1)
772            {
773                if(function_exists('getallheaders'))
774                {
775                    $this->debugmsg(''); // empty line
776                    foreach(getallheaders() as $name => $val)
777                    {
778                        $this->debugmsg("HEADER: $name: $val");
779                    }
780                }
781
782            }
783
784            if(isset($_SERVER['HTTP_CONTENT_ENCODING']))
785            {
786                $content_encoding = str_replace('x-', '', $_SERVER['HTTP_CONTENT_ENCODING']);
787            }
788            else
789            {
790                $content_encoding = '';
791            }
792
793            // check if request body has been compressed and decompress it
794            if($content_encoding != '' && strlen($data))
795            {
796                if($content_encoding == 'deflate' || $content_encoding == 'gzip')
797                {
798                    // if decoding works, use it. else assume data wasn't gzencoded
799                    if(function_exists('gzinflate') && in_array($content_encoding, $this->accepted_compression))
800                    {
801                        if($content_encoding == 'deflate' && $degzdata = @gzuncompress($data))
802                        {
803                            $data = $degzdata;
804                            if($this->debug > 1)
805                            {
806                                $this->debugmsg("\n+++INFLATED REQUEST+++[".strlen($data)." chars]+++\n" . $data . "\n+++END+++");
807                            }
808                        }
809                        elseif($content_encoding == 'gzip' && $degzdata = @gzinflate(substr($data, 10)))
810                        {
811                            $data = $degzdata;
812                            if($this->debug > 1)
813                                $this->debugmsg("+++INFLATED REQUEST+++[".strlen($data)." chars]+++\n" . $data . "\n+++END+++");
814                        }
815                        else
816                        {
817                            $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['server_decompress_fail'], $GLOBALS['xmlrpcstr']['server_decompress_fail']);
818                            return $r;
819                        }
820                    }
821                    else
822                    {
823                        //error_log('The server sent deflated data. Your php install must have the Zlib extension compiled in to support this.');
824                        $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['server_cannot_decompress'], $GLOBALS['xmlrpcstr']['server_cannot_decompress']);
825                        return $r;
826                    }
827                }
828            }
829
830            // check if client specified accepted charsets, and if we know how to fulfill
831            // the request
832            if ($this->response_charset_encoding == 'auto')
833            {
834                $resp_encoding = '';
835                if (isset($_SERVER['HTTP_ACCEPT_CHARSET']))
836                {
837                    // here we should check if we can match the client-requested encoding
838                    // with the encodings we know we can generate.
839                    /// @todo we should parse q=0.x preferences instead of getting first charset specified...
840                    $client_accepted_charsets = explode(',', strtoupper($_SERVER['HTTP_ACCEPT_CHARSET']));
841                    // Give preference to internal encoding
842                    $known_charsets = array($GLOBALS['xmlrpc_internalencoding'], 'UTF-8', 'ISO-8859-1', 'US-ASCII');
843                    foreach ($known_charsets as $charset)
844                    {
845                        foreach ($client_accepted_charsets as $accepted)
846                            if (strpos($accepted, $charset) === 0)
847                            {
848                                $resp_encoding = $charset;
849                                break;
850                            }
851                        if ($resp_encoding)
852                            break;
853                    }
854                }
855            }
856            else
857            {
858                $resp_encoding = $this->response_charset_encoding;
859            }
860
861            if (isset($_SERVER['HTTP_ACCEPT_ENCODING']))
862            {
863                $resp_compression = $_SERVER['HTTP_ACCEPT_ENCODING'];
864            }
865            else
866            {
867                $resp_compression = '';
868            }
869
870            // 'guestimate' request encoding
871            /// @todo check if mbstring is enabled and automagic input conversion is on: it might mingle with this check???
872            $req_encoding = guess_encoding(isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : '',
873                $data);
874
875            return null;
876        }
877
878        /**
879        * Parse an xml chunk containing an xmlrpc request and execute the corresponding
880        * php function registered with the server
881        * @param string $data the xml request
882        * @param string $req_encoding (optional) the charset encoding of the xml request
883        * @return xmlrpcresp
884        * @access private
885        */
886        function parseRequest($data, $req_encoding='')
887        {
888            // 2005/05/07 commented and moved into caller function code
889            //if($data=='')
890            //{
891            //  $data=$GLOBALS['HTTP_RAW_POST_DATA'];
892            //}
893
894            // G. Giunta 2005/02/13: we do NOT expect to receive html entities
895            // so we do not try to convert them into xml character entities
896            //$data = xmlrpc_html_entity_xlate($data);
897
898            $GLOBALS['_xh']=array();
899            $GLOBALS['_xh']['ac']='';
900            $GLOBALS['_xh']['stack']=array();
901            $GLOBALS['_xh']['valuestack'] = array();
902            $GLOBALS['_xh']['params']=array();
903            $GLOBALS['_xh']['pt']=array();
904            $GLOBALS['_xh']['isf']=0;
905            $GLOBALS['_xh']['isf_reason']='';
906            $GLOBALS['_xh']['method']=false; // so we can check later if we got a methodname or not
907            $GLOBALS['_xh']['rt']='';
908
909            // decompose incoming XML into request structure
910            if ($req_encoding != '')
911            {
912                if (!in_array($req_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
913                // the following code might be better for mb_string enabled installs, but
914                // makes the lib about 200% slower...
915                //if (!is_valid_charset($req_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
916                {
917                    error_log('XML-RPC: xmlrpc_server::parseRequest: invalid charset encoding of received request: '.$req_encoding);
918                    $req_encoding = $GLOBALS['xmlrpc_defencoding'];
919                }
920                /// @BUG this will fail on PHP 5 if charset is not specified in the xml prologue,
921                // the encoding is not UTF8 and there are non-ascii chars in the text...
922                /// @todo use an ampty string for php 5 ???
923                $parser = xml_parser_create($req_encoding);
924            }
925            else
926            {
927                $parser = xml_parser_create();
928            }
929
930            xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
931            // G. Giunta 2005/02/13: PHP internally uses ISO-8859-1, so we have to tell
932            // the xml parser to give us back data in the expected charset
933            // What if internal encoding is not in one of the 3 allowed?
934            // we use the broadest one, ie. utf8
935            // This allows to send data which is native in various charset,
936            // by extending xmlrpc_encode_entitites() and setting xmlrpc_internalencoding
937            if (!in_array($GLOBALS['xmlrpc_internalencoding'], array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
938            {
939                xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
940            }
941            else
942            {
943                xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $GLOBALS['xmlrpc_internalencoding']);
944            }
945
946            if ($this->functions_parameters_type != 'xmlrpcvals')
947                xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast');
948            else
949                xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee');
950            xml_set_character_data_handler($parser, 'xmlrpc_cd');
951            xml_set_default_handler($parser, 'xmlrpc_dh');
952            if(!xml_parse($parser, $data, 1))
953            {
954                // return XML error as a faultCode
955                $r=&new xmlrpcresp(0,
956                $GLOBALS['xmlrpcerrxml']+xml_get_error_code($parser),
957                sprintf('XML error: %s at line %d, column %d',
958                    xml_error_string(xml_get_error_code($parser)),
959                    xml_get_current_line_number($parser), xml_get_current_column_number($parser)));
960                xml_parser_free($parser);
961            }
962            elseif ($GLOBALS['_xh']['isf'])
963            {
964                xml_parser_free($parser);
965                $r=&new xmlrpcresp(0,
966                    $GLOBALS['xmlrpcerr']['invalid_request'],
967                    $GLOBALS['xmlrpcstr']['invalid_request'] . ' ' . $GLOBALS['_xh']['isf_reason']);
968            }
969            else
970            {
971                xml_parser_free($parser);
972                if ($this->functions_parameters_type != 'xmlrpcvals')
973                {
974                    if($this->debug > 1)
975                    {
976                        $this->debugmsg("\n+++PARSED+++\n".var_export($GLOBALS['_xh']['params'], true)."\n+++END+++");
977                    }
978                    $r = $this->execute($GLOBALS['_xh']['method'], $GLOBALS['_xh']['params'], $GLOBALS['_xh']['pt']);
979                }
980                else
981                {
982                    // build an xmlrpcmsg object with data parsed from xml
983                    $m=&new xmlrpcmsg($GLOBALS['_xh']['method']);
984                    // now add parameters in
985                    for($i=0; $i<count($GLOBALS['_xh']['params']); $i++)
986                    {
987                        $m->addParam($GLOBALS['_xh']['params'][$i]);
988                    }
989
990                    if($this->debug > 1)
991                    {
992                        $this->debugmsg("\n+++PARSED+++\n".var_export($m, true)."\n+++END+++");
993                    }
994                    $r = $this->execute($m);
995                }
996            }
997            return $r;
998        }
999
1000        /**
1001        * Execute a method invoked by the client, checking parameters used
1002        * @param mixed $m either an xmlrpcmsg obj or a method name
1003        * @param array $params array with method parameters as php types (if m is method name only)
1004        * @param array $paramtypes array with xmlrpc types of method parameters (if m is method name only)
1005        * @return xmlrpcresp
1006        * @access private
1007        */
1008        function execute($m, $params=null, $paramtypes=null)
1009        {
1010            if (is_object($m))
1011            {
1012                $methName = $m->method();
1013            }
1014            else
1015            {
1016                $methName = $m;
1017            }
1018            $sysCall = $this->allow_system_funcs && (strpos($methName, "system.") === 0);
1019            $dmap = $sysCall ? $GLOBALS['_xmlrpcs_dmap'] : $this->dmap;
1020
1021            if(!isset($dmap[$methName]['function']))
1022            {
1023                // No such method
1024                return new xmlrpcresp(0,
1025                    $GLOBALS['xmlrpcerr']['unknown_method'],
1026                    $GLOBALS['xmlrpcstr']['unknown_method']);
1027            }
1028
1029            // Check signature
1030            if(isset($dmap[$methName]['signature']))
1031            {
1032                $sig = $dmap[$methName]['signature'];
1033                if (is_object($m))
1034                {
1035                    list($ok, $errstr) = $this->verifySignature($m, $sig);
1036                }
1037                else
1038                {
1039                    list($ok, $errstr) = $this->verifySignature($paramtypes, $sig);
1040                }
1041                if(!$ok)
1042                {
1043                    // Didn't match.
1044                    return new xmlrpcresp(
1045                        0,
1046                        $GLOBALS['xmlrpcerr']['incorrect_params'],
1047                        $GLOBALS['xmlrpcstr']['incorrect_params'] . ": ${errstr}"
1048                    );
1049                }
1050            }
1051
1052            $func = $dmap[$methName]['function'];
1053            // let the 'class::function' syntax be accepted in dispatch maps
1054            if(is_string($func) && strpos($func, '::'))
1055            {
1056                $func = explode('::', $func);
1057            }
1058            // verify that function to be invoked is in fact callable
1059            if(!is_callable($func))
1060            {
1061                error_log("XML-RPC: xmlrpc_server::execute: function $func registered as method handler is not callable");
1062                return new xmlrpcresp(
1063                    0,
1064                    $GLOBALS['xmlrpcerr']['server_error'],
1065                    $GLOBALS['xmlrpcstr']['server_error'] . ": no function matches method"
1066                );
1067            }
1068
1069            // If debug level is 3, we should catch all errors generated during
1070            // processing of user function, and log them as part of response
1071            if($this->debug > 2)
1072            {
1073                $GLOBALS['_xmlrpcs_prev_ehandler'] = set_error_handler('_xmlrpcs_errorHandler');
1074            }
1075            if (is_object($m))
1076            {
1077                if($sysCall)
1078                {
1079                    $r = call_user_func($func, $this, $m);
1080                }
1081                else
1082                {
1083                    $r = call_user_func($func, $m);
1084                }
1085                if (!is_a($r, 'xmlrpcresp'))
1086                {
1087                    error_log("XML-RPC: xmlrpc_server::execute: function $func registered as method handler does not return an xmlrpcresp object");
1088                    if (is_a($r, 'xmlrpcval'))
1089                    {
1090                        $r =& new xmlrpcresp($r);
1091                    }
1092                    else
1093                    {
1094                        $r =& new xmlrpcresp(
1095                            0,
1096                            $GLOBALS['xmlrpcerr']['server_error'],
1097                            $GLOBALS['xmlrpcstr']['server_error'] . ": function does not return xmlrpcresp object"
1098                        );
1099                    }
1100                }
1101            }
1102            else
1103            {
1104                // call a 'plain php' function
1105                if($sysCall)
1106                {
1107                    array_unshift($params, $this);
1108                    $r = call_user_func_array($func, $params);
1109                }
1110                else
1111                {
1112                    // 3rd API convention for method-handling functions: EPI-style
1113                    if ($this->functions_parameters_type == 'epivals')
1114                    {
1115                        $r = call_user_func_array($func, array($methName, $params, $this->user_data));
1116                        // mimic EPI behaviour: if we get an array that looks like an error, make it
1117                        // an eror response
1118                        if (is_array($r) && array_key_exists('faultCode', $r) && array_key_exists('faultString', $r))
1119                        {
1120                            $r =& new xmlrpcresp(0, (integer)$r['faultCode'], (string)$r['faultString']);
1121                        }
1122                        else
1123                        {
1124                            // functions using EPI api should NOT return resp objects,
1125                            // so make sure we encode the return type correctly
1126                            $r =& new xmlrpcresp(php_xmlrpc_encode($r, array('extension_api')));
1127                        }
1128                    }
1129                    else
1130                    {
1131                        $r = call_user_func_array($func, $params);
1132                    }
1133                }
1134                // the return type can be either an xmlrpcresp object or a plain php value...
1135                if (!is_a($r, 'xmlrpcresp'))
1136                {
1137                    // what should we assume here about automatic encoding of datetimes
1138                    // and php classes instances???
1139                    $r =& new xmlrpcresp(php_xmlrpc_encode($r, array('auto_dates')));
1140                }
1141            }
1142            if($this->debug > 2)
1143            {
1144                // note: restore the error handler we found before calling the
1145                // user func, even if it has been changed inside the func itself
1146                if($GLOBALS['_xmlrpcs_prev_ehandler'])
1147                {
1148                    set_error_handler($GLOBALS['_xmlrpcs_prev_ehandler']);
1149                }
1150                else
1151                {
1152                    restore_error_handler();
1153                }
1154            }
1155            return $r;
1156        }
1157
1158        /**
1159        * add a string to the 'internal debug message' (separate from 'user debug message')
1160        * @param string $strings
1161        * @access private
1162        */
1163        function debugmsg($string)
1164        {
1165            $this->debug_info .= $string."\n";
1166        }
1167
1168        /**
1169        * @access private
1170        */
1171        function xml_header($charset_encoding='')
1172        {
1173            if ($charset_encoding != '')
1174            {
1175                return "<?xml version=\"1.0\" encoding=\"$charset_encoding\"?" . ">\n";
1176            }
1177            else
1178            {
1179                return "<?xml version=\"1.0\"?" . ">\n";
1180            }
1181        }
1182
1183        /**
1184        * A debugging routine: just echoes back the input packet as a string value
1185        * DEPRECATED!
1186        */
1187        function echoInput()
1188        {
1189            $r=&new xmlrpcresp(new xmlrpcval( "'Aha said I: '" . $GLOBALS['HTTP_RAW_POST_DATA'], 'string'));
1190            print $r->serialize();
1191        }
1192    }
1193?>
Note: See TracBrowser for help on using the repository browser.