Source code for file /phpxmlrpc/xmlrpcs.php
Documentation is available at xmlrpcs.php
// by Edd Dumbill (C) 1999-2002
// $Id: xmlrpcs.inc,v 1.57 2006/03/20 13:48:25 ggiunta Exp $
// Copyright (c) 1999,2000,2002 Edd Dumbill.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of the "XML-RPC for PHP" nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.
// listMethods: signature was either a string, or nothing.
// The useless string variant has been removed
$_xmlrpcs_listMethods_sig=
array(array($GLOBALS['xmlrpcArray']));
$_xmlrpcs_listMethods_doc=
'This method lists all the methods that the XML-RPC server knows how to dispatch';
function _xmlrpcs_listMethods($server, $m=
null) // if called in plain php values mode, second param is missing
foreach($server->dmap as $key =>
$val)
if($server->allow_system_funcs)
foreach($GLOBALS['_xmlrpcs_dmap'] as $key =>
$val)
$_xmlrpcs_methodSignature_sig=
array(array($GLOBALS['xmlrpcArray'], $GLOBALS['xmlrpcString']));
$_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)';
// let accept as parameter both an xmlrpcval or string
$methName=
$m->getParam(0);
$methName=
$methName->scalarval();
if(ereg("^system\.", $methName))
$dmap=
$GLOBALS['_xmlrpcs_dmap']; $sysCall=
1;
$dmap=
$server->dmap; $sysCall=
0;
// print "<!-- ${methName} -->\n";
if(isset
($dmap[$methName]))
if(isset
($dmap[$methName]['signature']))
foreach($dmap[$methName]['signature'] as $inSig)
// NB: according to the official docs, we should be returning a
// "none-array" here, which means not-an-array
$r=
&new xmlrpcresp(0,$GLOBALS['xmlrpcerr']['introspect_unknown'], $GLOBALS['xmlrpcstr']['introspect_unknown']);
$_xmlrpcs_methodHelp_sig=
array(array($GLOBALS['xmlrpcString'], $GLOBALS['xmlrpcString']));
$_xmlrpcs_methodHelp_doc=
'Returns help text if defined for the method passed, otherwise returns an empty string';
// let accept as parameter both an xmlrpcval or string
$methName=
$m->getParam(0);
$methName=
$methName->scalarval();
if(ereg("^system\.", $methName))
$dmap=
$GLOBALS['_xmlrpcs_dmap']; $sysCall=
1;
$dmap=
$server->dmap; $sysCall=
0;
// print "<!-- ${methName} -->\n";
if(isset
($dmap[$methName]))
if(isset
($dmap[$methName]['docstring']))
$r=
&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['introspect_unknown'], $GLOBALS['xmlrpcstr']['introspect_unknown']);
$_xmlrpcs_multicall_sig =
array(array($GLOBALS['xmlrpcArray'], $GLOBALS['xmlrpcArray']));
$_xmlrpcs_multicall_doc =
'Boxcar multiple RPC calls in one request. See http://www.xmlrpc.com/discuss/msgReader$1208 for details';
$str =
$GLOBALS['xmlrpcstr']["multicall_${err}"];
$code =
$GLOBALS['xmlrpcerr']["multicall_${err}"];
$code =
$err->faultCode();
$str =
$err->faultString();
$struct['faultCode'] =
new xmlrpcval($code, 'int');
$struct['faultString'] =
new xmlrpcval($str, 'string');
if($call->kindOf() !=
'struct')
$methName =
@$call->structmem('methodName');
if($methName->kindOf() !=
'scalar' ||
$methName->scalartyp() !=
'string')
if($methName->scalarval() ==
'system.multicall')
$params =
@$call->structmem('params');
if($params->kindOf() !=
'array')
$numParams =
$params->arraysize();
$msg =
new xmlrpcmsg($methName->scalarval());
for($i =
0; $i <
$numParams; $i++
)
if(!$msg->addParam($params->arraymem($i)))
$GLOBALS['xmlrpcerr']['incorrect_params'],
$GLOBALS['xmlrpcstr']['incorrect_params'] .
": probable xml error in param " .
$i));
$result =
$server->execute($msg);
if($result->faultCode() !=
0)
return new xmlrpcval(array($result->value()), 'array');
if($call['methodName'] ==
'system.multicall')
// this is a real dirty and simplistic hack, since we might have received a
// base64 or datetime values, but they will be listed as strings here...
$numParams =
count($call['params']);
foreach($call['params'] as $val)
$result =
$server->execute($call['methodName'], $call['params'], $pt);
if($result->faultCode() !=
0)
return new xmlrpcval(array($result->value()), 'array');
// let accept a plain list of php parameters, beside a single xmlrpc msg object
$calls =
$m->getParam(0);
$numCalls =
$calls->arraysize();
for($i =
0; $i <
$numCalls; $i++
)
$call =
$calls->arraymem($i);
//$calls = func_get_args();
for($i =
0; $i <
$numCalls; $i++
)
$GLOBALS['_xmlrpcs_dmap']=
array(
'system.listMethods' =>
array(
'function' =>
'_xmlrpcs_listMethods',
'signature' =>
$_xmlrpcs_listMethods_sig,
'docstring' =>
$_xmlrpcs_listMethods_doc),
'system.methodHelp' =>
array(
'function' =>
'_xmlrpcs_methodHelp',
'signature' =>
$_xmlrpcs_methodHelp_sig,
'docstring' =>
$_xmlrpcs_methodHelp_doc),
'system.methodSignature' =>
array(
'function' =>
'_xmlrpcs_methodSignature',
'signature' =>
$_xmlrpcs_methodSignature_sig,
'docstring' =>
$_xmlrpcs_methodSignature_doc),
'system.multicall' =>
array(
'function' =>
'_xmlrpcs_multicall',
'signature' =>
$_xmlrpcs_multicall_sig,
'docstring' =>
$_xmlrpcs_multicall_doc
$GLOBALS['_xmlrpcs_occurred_errors'] =
'';
$GLOBALS['_xmlrpcs_prev_ehandler'] =
'';
* Error handler used to track errors that occur during server-side execution of PHP code.
* This allows to report back to the client whether an internal error has occurred or not
* using an xmlrpc response object, instead of letting the client deal with the html junk
* that a PHP execution error on the server generally entails.
* NB: in fact a user defined error handler can only handle WARNING, NOTICE and USER_* errors.
//if($errcode != E_NOTICE && $errcode != E_WARNING && $errcode != E_USER_NOTICE && $errcode != E_USER_WARNING)
if($errcode !=
2048) // do not use E_STRICT by name, since on PHP 4 it will not be defined
$GLOBALS['_xmlrpcs_occurred_errors'] =
$GLOBALS['_xmlrpcs_occurred_errors'] .
$errstring .
"\n";
// Try to avoid as much as possible disruption to the previous error handling
if($GLOBALS['_xmlrpcs_prev_ehandler'] ==
'')
// The previous error handler was the default: all we should do is log error
// to the default error log (if level high enough)
// Pass control on to previous error handler, trying to avoid loops...
if($GLOBALS['_xmlrpcs_prev_ehandler'] !=
'_xmlrpcs_errorHandler')
// NB: this code will NOT work on php < 4.0.2: only 2 params were used for error handlers
if(is_array($GLOBALS['_xmlrpcs_prev_ehandler']))
$GLOBALS['_xmlrpcs_prev_ehandler'][0]->$GLOBALS['_xmlrpcs_prev_ehandler'][1]($errcode, $errstring, $filename, $lineno, $context);
$GLOBALS['_xmlrpcs_prev_ehandler']($errcode, $errstring, $filename, $lineno, $context);
$GLOBALS['_xmlrpc_debuginfo']=
'';
* Add a string to the debug info that can be later seralized by the server
* as part of the response message.
* Note that for best compatbility, the debug string should be encoded using
* the $GLOBALS['xmlrpc_internalencoding'] character set.
$GLOBALS['_xmlrpc_debuginfo'] .=
$m .
"\n";
/// array defining php functions exposed as xmlrpc methods by this server
* Defines how functions in dmap will be invokde: either using an xmlrpc msg object
* valid strings are 'xmlrpcvals' or 'phpvals'
/// 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
* When set to true, it will enable HTTP compression of the response, in case
* the client has declared its support for compression in the request.
* List of http compression methods accepted by the server for requests.
* NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib
/// shall we serve calls to system.* methods?
/// list of charset encodings natively accepted for requests
* charset encoding to be used for response.
* NB: if we can, we will convert the generated response from internal_encoding to the intended one.
* can be: a supported xml encoding (only UTF-8 and ISO-8859-1 at present, unless mbstring is enabled),
* null (leave unspecified in response, convert output stream to US_ASCII),
* 'default' (use xmlrpc library default as specified in xmlrpc.inc, convert output stream if needed),
* 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).
* NB: pretty dangerous if you accept every charset and do not have mbstring enabled)
/// storage for internal debug info
* @param array $dispmap the dispatch map withd efinition of exposed services
* @param boolean $servicenow set to false to prevent the server from runnung upon construction
// if ZLIB is enabled, let the server by default accept compressed requests,
// and compress responses sent to clients that support them
// by default the xml parser can support these 3 charset encodings
// dispMap is a dispatch array of methods
// mapped to function names and signatures
// doesn't appear in the map then an unknown
// method error is generated
/* milosch - changed to make passing dispMap optional.
* instead, you can use the class add_to_map() function
* to add functions manually (borrowed from SOAPX4)
* Set debug level of server.
* @param integer $in debug lvl: determines info added to xmlrpc responses (as xml comments)
* 1 = msgs set from user with debugmsg(),
* 2 = add complete xmlrpc request (headers and body),
* 3 = add also all processing warnings happened during method processing
* (NB: this involves setting a custom error handler, and might interfere
* with the standard processing of the php function exposed as method. In
* particular, triggering an USER_ERROR level error will not halt script
* execution anymore, but just end up logged in the xmlrpc response)
* Note that info added at elevel 2 and 3 will be base64 encoded
* Return a string with the serialized representation of all debug info
* @param string $charset_encoding the target charset encoding for the serialization
* @return string an XML comment (or two)
// Tough encoding problem: which internal charset should we assume for debug info?
// It might contain a copy of raw data received from client, ie with unknown encoding,
// intermixed with php generated data and user generated data...
// so we split it: system debug is base 64 encoded,
// user debug info should be encoded by the end user using the INTERNAL_ENCODING
if($GLOBALS['_xmlrpc_debuginfo']!=
'')
$out .=
"<!-- DEBUG INFO:\n" .
xmlrpc_encode_entitites(str_replace('--', '_-', $GLOBALS['_xmlrpc_debuginfo']), $GLOBALS['xmlrpc_internalencoding'], $charset_encoding) .
"\n-->\n";
// NB: a better solution MIGHT be to use CDATA, but we need to insert it
// into return payload AFTER the beginning tag
//$out .= "<![CDATA[ DEBUG INFO:\n\n" . str_replace(']]>', ']_]_>', $GLOBALS['_xmlrpc_debuginfo']) . "\n]]>\n";
* Execute the xmlrpc request, printing the response
* @param string $data the request body. If null, the http POST request will be examined
$data = isset
($GLOBALS['HTTP_RAW_POST_DATA']) ?
$GLOBALS['HTTP_RAW_POST_DATA'] :
'';
// reset internal debug info
// Echo back what we received, before parsing it
$this->debugmsg("+++GOT+++\n" .
$data .
"\n+++END+++");
$r =
$this->parseRequestHeaders($data, $req_charset, $resp_charset, $resp_encoding);
$r=
$this->parseRequest($data, $req_charset);
if($this->debug >
2 &&
$GLOBALS['_xmlrpcs_occurred_errors'])
$this->debugmsg("+++PROCESSING ERRORS AND WARNINGS+++\n" .
$GLOBALS['_xmlrpcs_occurred_errors'] .
"+++END+++");
//$payload='<?xml version="1.0" encoding="' . $GLOBALS['xmlrpc_defencoding'] . '"?' . '>' . "\n"
//$payload = $payload . "<methodResponse>\n" . $this->serializeDebug();
//$payload = $payload . substr($r->serialize(), 17);
// G. Giunta 2006-01-27: do not create response serialization if it has
// already happened. Helps building json magic
$r->serialize($resp_charset);
$payload =
$payload .
$r->payload;
// if we get a warning/error that has output some text before here, then we cannot
// add a new header. We cannot say we are sending xml, either...
header('Content-Type: '.
$r->content_type);
// we do not know if client actually told us an accepted charset, but if he did
// we have to tell him what we did
header("Vary: Accept-Charset");
// http compression of output
if(strstr($resp_encoding, 'gzip'))
header("Content-Encoding: gzip");
header("Vary: Accept-Encoding");
elseif (strstr($resp_encoding, 'deflate'))
header("Content-Encoding: deflate");
header("Vary: Accept-Encoding");
//print "Internal server error: headers sent before PHP response"
error_log('XML-RPC: xmlrpc_server::service: http headers already sent before response is fully generated. Check for php warning or error messages');
* Add a method to the dispatch map
* @param string $methodname the name with which the method will be made available
* @param string $function the php function that will get invoked
* @param array $sig the array of valid method signatures
* @param string $doc method documentation
function add_to_map($methodname,$function,$sig,$doc=
'')
$this->dmap[$methodname] =
array(
* Verify type and number of parameters received against a list of known signatures
* @param array $in array of either xmlrpcval objects or xmlrpc type definitions
* @param array $sig array of known signatures to match against
function verifySignature($in, $sig)
// check each possible signature in turn
$numParams =
$in->getNumParams();
if(sizeof($cursig)==
$numParams+
1)
for($n=
0; $n<
$numParams; $n++
)
if($p->kindOf() ==
'scalar')
$pt=
$in[$n] ==
'i4' ?
'int' :
$in[$n]; // dispatch maps never use i4...
// param index is $n+1, as first member of sig is return type
if($pt !=
$cursig[$n+
1] &&
$cursig[$n+
1] !=
$GLOBALS['xmlrpcValue'])
return array(0, "Wanted ${wanted}, got ${got} at param ${pno}");
return array(0, "No method signature matches number of parameters");
* Parse http headers received along with xmlrpc request. If needed, inflate request
* @return null on success or an xmlrpcresp
function parseRequestHeaders(&$data, &$req_encoding, &$resp_encoding, &$resp_compression)
// Play nice to PHP 4.0.x: superglobals were not yet invented...
$_SERVER =
$GLOBALS['HTTP_SERVER_VARS'];
$this->debugmsg(''); // empty line
$this->debugmsg("HEADER: $name: $val");
if(isset
($_SERVER['HTTP_CONTENT_ENCODING']))
$content_encoding =
$_SERVER['HTTP_CONTENT_ENCODING'];
// check if request body has been compressed and decompress it
if($content_encoding !=
'' &&
strlen($data))
if($content_encoding ==
'deflate' ||
$content_encoding ==
'gzip')
// if decoding works, use it. else assume data wasn't gzencoded
if($content_encoding ==
'deflate' &&
$degzdata =
@gzinflate($data))
$this->debugmsg("\n+++INFLATED REQUEST+++[".
strlen($data).
" chars]+++\n" .
$data .
"\n+++END+++");
elseif($content_encoding ==
'gzip' &&
$degzdata =
@gzinflate(substr($data, 10)))
$this->debugmsg("+++INFLATED REQUEST+++[".
strlen($data).
" chars]+++\n" .
$data .
"\n+++END+++");
$r =
new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['server_decompress_fail'], $GLOBALS['xmlrpcstr']['server_decompress_fail']);
//error_log('The server sent deflated data. Your php install must have the Zlib extension compiled in to support this.');
$r =
new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['server_cannot_decompress'], $GLOBALS['xmlrpcstr']['server_cannot_decompress']);
// check if client specified accepted charsets, and if we know how to fulfill
if (isset
($_SERVER['HTTP_ACCEPT_CHARSET']))
// here we should check if we can match the client-requested encoding
// with the encodings we know we can generate.
/// @todo we should parse q=0.x preferences instead of getting first charset specified...
$client_accepted_charsets =
explode(',', strtoupper($_SERVER['HTTP_ACCEPT_CHARSET']));
// Give preference to internal encoding
$known_charsets =
array($this->internal_encoding, 'UTF-8', 'ISO-8859-1', 'US-ASCII');
foreach ($known_charsets as $charset)
foreach ($client_accepted_charsets as $accepted)
if (strpos($accepted, $charset) ===
0)
$resp_encoding =
$charset;
if (isset
($_SERVER['HTTP_ACCEPT_ENCODING']))
$resp_compression =
$_SERVER['HTTP_ACCEPT_ENCODING'];
// 'guestimate' request encoding
/// @todo check if mbstring is enabled and automagic input conversion is on: it might mingle with this check???
$req_encoding =
guess_encoding(isset
($_SERVER['CONTENT_TYPE']) ?
$_SERVER['CONTENT_TYPE'] :
'',
* Parse an xml chunk containing an xmlrpc request and execute the corresponding
* php function registered with the server
* @param string $data the xml request
* @param string $req_encoding (optional) the charset encoding of the xml request
function parseRequest($data, $req_encoding=
'')
// 2005/05/07 commented and moved into caller function code
// $data=$GLOBALS['HTTP_RAW_POST_DATA'];
// G. Giunta 2005/02/13: we do NOT expect to receive html entities
// so we do not try to convert them into xml character entities
//$data = xmlrpc_html_entity_xlate($data);
$GLOBALS['_xh']['isf']=
0;
$GLOBALS['_xh']['isf_reason']=
'';
$GLOBALS['_xh']['params']=
array();
$GLOBALS['_xh']['pt']=
array();
$GLOBALS['_xh']['stack']=
array();
$GLOBALS['_xh']['valuestack'] =
array();
$GLOBALS['_xh']['method']=
'';
// decompose incoming XML into request structure
if (!in_array($req_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
// the following code might be better for mb_string enabled installs, but
// makes the lib about 200% slower...
//if (!is_valid_charset($req_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
error_log('XML-RPC: xmlrpc_server::parseRequest: invalid charset encoding of received request: '.
$req_encoding);
$req_encoding =
$GLOBALS['xmlrpc_defencoding'];
/// @BUG this will fail on PHP 5 if charset is not specified in the xml prologue,
// the encoding is not UTF8 and there are non-ascii chars in the text...
// G. Giunta 2005/02/13: PHP internally uses ISO-8859-1, so we have to tell
// the xml parser to give us back data in the expected charset
// return XML error as a faultCode
sprintf('XML error: %s at line %d, column %d',
elseif ($GLOBALS['_xh']['isf'])
$GLOBALS['xmlrpcerr']['invalid_request'],
$GLOBALS['xmlrpcstr']['invalid_request'] .
' ' .
$GLOBALS['_xh']['isf_reason']);
$this->debugmsg("\n+++PARSED+++\n".
var_export($GLOBALS['_xh']['params'], true).
"\n+++END+++");
$r =
$this->execute($GLOBALS['_xh']['method'], $GLOBALS['_xh']['params'], $GLOBALS['_xh']['pt']);
// build an xmlrpcmsg object with data parsed from xml
$m=
&new xmlrpcmsg($GLOBALS['_xh']['method']);
for($i=
0; $i<
sizeof($GLOBALS['_xh']['params']); $i++
)
$m->addParam($GLOBALS['_xh']['params'][$i]);
$this->debugmsg("\n+++PARSED+++\n".
var_export($m, true).
"\n+++END+++");
* Execute a method invoked by the client, checking parameters used
* @param mixed $m either an xmlrpcmsg obj or a method name
* @param array $params array with method parameters as php types (if m is method name only)
* @param array $paramtypes array with xmlrpc types of method parameters (if m is method name only)
function execute($m, $params=
null, $paramtypes=
null)
$methName =
$m->method();
$dmap =
$sysCall ?
$GLOBALS['_xmlrpcs_dmap'] :
$this->dmap;
if(!isset
($dmap[$methName]['function']))
$GLOBALS['xmlrpcerr']['unknown_method'],
$GLOBALS['xmlrpcstr']['unknown_method']);
if(isset
($dmap[$methName]['signature']))
$sig =
$dmap[$methName]['signature'];
list
($ok, $errstr) =
$this->verifySignature($m, $sig);
list
($ok, $errstr) =
$this->verifySignature($paramtypes, $sig);
$GLOBALS['xmlrpcerr']['incorrect_params'],
$GLOBALS['xmlrpcstr']['incorrect_params'] .
": ${errstr}"
$func =
$dmap[$methName]['function'];
// let the 'class::function' syntax be accepted in dispatch maps
// verify that function to be invoked is in fact callable
error_log("XML-RPC: xmlrpc_server::execute: function $func registered as method handler is not callable");
$GLOBALS['xmlrpcerr']['server_error'],
$GLOBALS['xmlrpcstr']['server_error'] .
": no function matches method"
// If debug level is 3, we should catch all errors generated during
// processing of user function, and log them as part of response
if (!is_a($r, 'xmlrpcresp'))
error_log("XML-RPC: xmlrpc_server::execute: function $func registered as method handler does not return an xmlrpcresp object");
if (is_a($r, 'xmlrpcval'))
$GLOBALS['xmlrpcerr']['server_error'],
$GLOBALS['xmlrpcstr']['server_error'] .
": function does not return xmlrpcresp object"
// call a 'plain php' function
// the return type can be either an xmlrpcresp object or a plain php value...
if (!is_a($r, 'xmlrpcresp'))
// what should we assume here about automatic encoding of datetimes
// and php classes instances???
// note: restore the error handler we found before calling the
// user func, even if it has been changed inside the func itself
if($GLOBALS['_xmlrpcs_prev_ehandler'])
* add a string to the 'internal debug message' (separate from 'user debug message')
function debugmsg($string)
if ($charset_encoding !=
'')
return "<?xml version=\"1.0\" encoding=\"$charset_encoding\"?" .
">\n";
return "<?xml version=\"1.0\"?" .
">\n";
* A debugging routine: just echoes back the input packet as a string value
$r=
&new xmlrpcresp(new xmlrpcval( "'Aha said I: '" .
$GLOBALS['HTTP_RAW_POST_DATA'], 'string'));