OSDN Git Service

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