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