From: ggiunta Date: Sat, 5 Sep 2009 15:14:26 +0000 (+0000) Subject: * xmlrpcs.inc, xmlrpcs.inc: remove code that was left for compatibility with php... X-Git-Tag: 3.0.0-beta~5 X-Git-Url: http://git.onelab.eu/?p=plcapi.git;a=commitdiff_plain;h=8346dc34a350ab95630faac56fca3663a4b8e49b * xmlrpcs.inc, xmlrpcs.inc: remove code that was left for compatibility with php 4; use __METHOD__ constant for error messages instead of hardcoded values * xmlrpcs.inc: catch exceptions thrown during execution of invoked methods; check for $_SERVER having been disabled via php.ini and log an error if so * server.php, testsuite.php: add a new test and server method for exception catching in the server git-svn-id: https://svn.code.sf.net/p/phpxmlrpc/code/trunk/xmlrpc@54 013ecfd8-0664-425d-a759-9c98391dc3f9 --- diff --git a/ChangeLog b/ChangeLog index 1aedc15..c8a5050 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,15 @@ +2009-09-05 - G. Giunta (giunta.gaetano@gmail.com) + + * xmlrpcs.inc, xmlrpcs.inc: remove code that was left for compatibility + with php 4; use __METHOD__ constant for error messages instead of hardcoded + values + + * xmlrpcs.inc: catch exceptions thrown during execution of invoked methods; + check for $_SERVER having been disabled via php.ini and log an error if so + + * server.php, testsuite.php: add a new test and server method for exception + catching in the server + 2009-08-05 - G. Giunta (giunta.gaetano@gmail.com) * xmlrpc_wrappers.inc: improve compatibility with php 5.0 when registering diff --git a/demo/server/server.php b/demo/server/server.php index 31f2225..bc8200e 100644 --- a/demo/server/server.php +++ b/demo/server/server.php @@ -36,6 +36,14 @@ if ($_SERVER['REQUEST_METHOD'] != 'POST' && isset($_GET['showSource'])) return new xmlrpcresp(new xmlrpcval(1, 'boolean')); } + /** + * Method used to testcatching of exceptions in the server. + */ + function exceptiongenerator($m) + { + throw new Exception("it's just a test", 1); + } + /** * a PHP version of the state-number server. Send me an integer and i'll sell you a state * @param integer $s @@ -693,6 +701,9 @@ mimetype, a string, is a standard MIME type, for example, text/plain. "function" => array($o, "phpwarninggenerator") //'function' => 'xmlrpc_server_methods_container::phpwarninggenerator' ), + "examples.raiseException" => array( + "function" => array($o, "exceptiongenerator") + ), "examples.getallheaders" => array( "function" => 'getallheaders_xmlrpc', "signature" => $getallheaders_sig, @@ -835,7 +846,8 @@ mimetype, a string, is a standard MIME type, for example, text/plain. // we do this to help the testsuite script: do not reproduce in production! if (isset($_GET['RESPONSE_ENCODING'])) $s->response_charset_encoding = $_GET['RESPONSE_ENCODING']; - + if (isset($_GET['EXCEPTION_HANDLING'])) + $s->exception_handling = $_GET['EXCEPTION_HANDLING']; $s->service(); // that should do all we need! ?> \ No newline at end of file diff --git a/lib/xmlrpc.inc b/lib/xmlrpc.inc index 4ac287a..74c2c25 100644 --- a/lib/xmlrpc.inc +++ b/lib/xmlrpc.inc @@ -1263,7 +1263,7 @@ $credentials='Authorization: Basic ' . base64_encode($username . ':' . $password) . "\r\n"; if ($authtype != 1) { - error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth is supported with HTTP 1.0'); + error_log('XML-RPC: '.__METHOD__.': warning. Only Basic auth is supported with HTTP 1.0'); } } @@ -1287,7 +1287,7 @@ { if ($proxyauthtype != 1) { - error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth to proxy is supported with HTTP 1.0'); + error_log('XML-RPC: '.__METHOD__.': warning. Only Basic auth to proxy is supported with HTTP 1.0'); } $proxy_credentials = 'Proxy-Authorization: Basic ' . base64_encode($proxyusername.':'.$proxypassword) . "\r\n"; } @@ -1563,7 +1563,7 @@ } else if ($authtype != 1) { - error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth is supported by the current PHP/curl install'); + error_log('XML-RPC: '.__METHOD__.': warning. Only Basic auth is supported by the current PHP/curl install'); } } @@ -1622,7 +1622,7 @@ } else if ($proxyauthtype != 1) { - error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth to proxy is supported by the current PHP/curl install'); + error_log('XML-RPC: '.__METHOD__.': warning. Only Basic auth to proxy is supported by the current PHP/curl install'); } } } @@ -1640,10 +1640,10 @@ curl_setopt($curl, CURLOPT_COOKIE, substr($cookieheader, 0, -2)); } - foreach ($this->extracurlopts as $opt => $val) - { - curl_setopt($curl, $opt, $val); - } + foreach ($this->extracurlopts as $opt => $val) + { + curl_setopt($curl, $opt, $val); + } $result = curl_exec($curl); @@ -2262,7 +2262,7 @@ xmlrpc_encode_entitites($this->errstr, $GLOBALS['xmlrpc_internalencoding'], $cha } else { - error_log('XML-RPC: xmlrpcmsg::parseResponse: HTTPS via proxy error, tunnel connection possibly failed'); + error_log('XML-RPC: '.__METHOD__.': HTTPS via proxy error, tunnel connection possibly failed'); $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $GLOBALS['xmlrpcstr']['http_error']. ' (HTTPS via proxy error, tunnel connection possibly failed)'); return $r; } @@ -2283,7 +2283,7 @@ xmlrpc_encode_entitites($this->errstr, $GLOBALS['xmlrpc_internalencoding'], $cha if(!preg_match('/^HTTP\/[0-9.]+ 200 /', $data)) { $errstr= substr($data, 0, strpos($data, "\n")-1); - error_log('XML-RPC: xmlrpcmsg::parseResponse: HTTP error, got response: ' .$errstr); + error_log('XML-RPC: '.__METHOD__.': HTTP error, got response: ' .$errstr); $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $GLOBALS['xmlrpcstr']['http_error']. ' (' . $errstr . ')'); return $r; } @@ -2408,7 +2408,7 @@ xmlrpc_encode_entitites($this->errstr, $GLOBALS['xmlrpc_internalencoding'], $cha { if(!$data = decode_chunked($data)) { - error_log('XML-RPC: xmlrpcmsg::parseResponse: errors occurred when trying to rebuild the chunked data received from server'); + error_log('XML-RPC: '.__METHOD__.': errors occurred when trying to rebuild the chunked data received from server'); $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['dechunk_fail'], $GLOBALS['xmlrpcstr']['dechunk_fail']); return $r; } @@ -2438,14 +2438,14 @@ xmlrpc_encode_entitites($this->errstr, $GLOBALS['xmlrpc_internalencoding'], $cha } else { - error_log('XML-RPC: xmlrpcmsg::parseResponse: errors occurred when trying to decode the deflated data received from server'); + error_log('XML-RPC: '.__METHOD__.': errors occurred when trying to decode the deflated data received from server'); $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['decompress_fail'], $GLOBALS['xmlrpcstr']['decompress_fail']); return $r; } } else { - error_log('XML-RPC: xmlrpcmsg::parseResponse: the server sent deflated data. Your php install must have the Zlib extension compiled in to support this.'); + error_log('XML-RPC: '.__METHOD__.': the server sent deflated data. Your php install must have the Zlib extension compiled in to support this.'); $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['cannot_decompress'], $GLOBALS['xmlrpcstr']['cannot_decompress']); return $r; } @@ -2453,7 +2453,7 @@ xmlrpc_encode_entitites($this->errstr, $GLOBALS['xmlrpc_internalencoding'], $cha } } // end of 'if needed, de-chunk, re-inflate response' - // real stupid hack to avoid PHP 4 complaining about returning NULL by ref + // real stupid hack to avoid PHP complaining about returning NULL by ref $r = null; $r =& $r; return $r; @@ -2477,7 +2477,7 @@ xmlrpc_encode_entitites($this->errstr, $GLOBALS['xmlrpc_internalencoding'], $cha if($data == '') { - error_log('XML-RPC: xmlrpcmsg::parseResponse: no response received from server.'); + error_log('XML-RPC: '.__METHOD__.': no response received from server.'); $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_data'], $GLOBALS['xmlrpcstr']['no_data']); return $r; } @@ -2522,17 +2522,10 @@ xmlrpc_encode_entitites($this->errstr, $GLOBALS['xmlrpc_internalencoding'], $cha // be tolerant of junk after methodResponse (e.g. javascript ads automatically inserted by free hosts) // idea from Luca Mariano originally in PEARified version of the lib - $bd = false; - // Poor man's version of strrpos for php 4... - $pos = strpos($data, ''); - while($pos || is_int($pos)) - { - $bd = $pos+17; - $pos = strpos($data, '', $bd); - } - if($bd) + $pos = strrpos($data, ''); + if($pos !== false) { - $data = substr($data, 0, $bd); + $data = substr($data, 0, $pos+17); } // if user wants back raw xml, give it to him @@ -2563,7 +2556,7 @@ xmlrpc_encode_entitites($this->errstr, $GLOBALS['xmlrpc_internalencoding'], $cha // makes the lib about 200% slower... //if (!is_valid_charset($resp_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) { - error_log('XML-RPC: xmlrpcmsg::parseResponse: invalid charset encoding of received response: '.$resp_encoding); + error_log('XML-RPC: '.__METHOD__.': invalid charset encoding of received response: '.$resp_encoding); $resp_encoding = $GLOBALS['xmlrpc_defencoding']; } $parser = xml_parser_create($resp_encoding); @@ -2738,7 +2731,7 @@ xmlrpc_encode_entitites($this->errstr, $GLOBALS['xmlrpc_internalencoding'], $cha $this->me['struct']=$val; break; default: - error_log("XML-RPC: xmlrpcval::xmlrpcval: not a known type ($type)"); + error_log("XML-RPC: ".__METHOD__.": not a known type ($type)"); } /*if($type=='') { @@ -2770,7 +2763,7 @@ xmlrpc_encode_entitites($this->errstr, $GLOBALS['xmlrpc_internalencoding'], $cha $typeof=@$GLOBALS['xmlrpcTypes'][$type]; if($typeof!=1) { - error_log("XML-RPC: xmlrpcval::addScalar: not a scalar type ($type)"); + error_log("XML-RPC: ".__METHOD__.": not a scalar type ($type)"); return 0; } @@ -2792,10 +2785,10 @@ xmlrpc_encode_entitites($this->errstr, $GLOBALS['xmlrpc_internalencoding'], $cha switch($this->mytype) { case 1: - error_log('XML-RPC: xmlrpcval::addScalar: scalar xmlrpcval can have only one value'); + error_log('XML-RPC: '.__METHOD__.': scalar xmlrpcval can have only one value'); return 0; case 3: - error_log('XML-RPC: xmlrpcval::addScalar: cannot add anonymous scalar to struct xmlrpcval'); + error_log('XML-RPC: '.__METHOD__.': cannot add anonymous scalar to struct xmlrpcval'); return 0; case 2: // we're adding a scalar value to an array here @@ -2837,7 +2830,7 @@ xmlrpc_encode_entitites($this->errstr, $GLOBALS['xmlrpc_internalencoding'], $cha } else { - error_log('XML-RPC: xmlrpcval::addArray: already initialized as a [' . $this->kindOf() . ']'); + error_log('XML-RPC: '.__METHOD__.': already initialized as a [' . $this->kindOf() . ']'); return 0; } } @@ -2866,7 +2859,7 @@ xmlrpc_encode_entitites($this->errstr, $GLOBALS['xmlrpc_internalencoding'], $cha } else { - error_log('XML-RPC: xmlrpcval::addStruct: already initialized as a [' . $this->kindOf() . ']'); + error_log('XML-RPC: '.__METHOD__.': already initialized as a [' . $this->kindOf() . ']'); return 0; } } @@ -2939,8 +2932,8 @@ xmlrpc_encode_entitites($this->errstr, $GLOBALS['xmlrpc_internalencoding'], $cha break; case $GLOBALS['xmlrpcDouble']: // avoid using standard conversion of float to string because it is locale-dependent, - // and also because the xmlrpc spec forbids exponential notation - // sprintf('%F') would be most likely ok but it is only available since PHP 4.3.10 and PHP 5.0.3. + // and also because the xmlrpc spec forbids exponential notation. + // sprintf('%F') could be most likely ok but it fails eg. on 2e-14. // The code below tries its best at keeping max precision while avoiding exp notation, // but there is of course no limit in the number of decimal places to be used... $rs.="<${typ}>".preg_replace('/\\.?0+$/','',number_format((double)$val, 128, '.', '')).""; diff --git a/lib/xmlrpcs.inc b/lib/xmlrpcs.inc index ea6ec13..2fafdae 100644 --- a/lib/xmlrpcs.inc +++ b/lib/xmlrpcs.inc @@ -365,6 +365,7 @@ $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 @@ -381,7 +382,7 @@ return; //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 + if($errcode != E_STRICT) { $GLOBALS['_xmlrpcs_occurred_errors'] = $GLOBALS['_xmlrpcs_occurred_errors'] . $errstring . "\n"; } @@ -443,16 +444,23 @@ * valid strings are 'xmlrpcvals', 'phpvals' or 'epivals' */ var $functions_parameters_type='xmlrpcvals'; - /** - * Option used for fine-tuning the encoding the php values returned from - * functions registered in the dispatch map when the functions_parameters_types - * member is set to 'phpvals' - * @see php_xmlrpc_encode for a list of values - */ - var $phpvals_encoding_options = array( 'auto_dates' ); + /** + * Option used for fine-tuning the encoding the php values returned from + * functions registered in the dispatch map when the functions_parameters_types + * member is set to 'phpvals' + * @see php_xmlrpc_encode for a list of values + */ + var $phpvals_encoding_options = array( 'auto_dates' ); /// 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 var $debug = 1; /** + * Controls behaviour of server when invoked user function throws an exception: + * 0 = catch it and return an 'internal error' xmlrpc response (default) + * 1 = catch it and return an xmlrpc response with the error corresponding to the exception + * 2 = allow the exception to float to the upper layers + */ + var $exception_handling = 0; + /** * 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. */ @@ -674,7 +682,7 @@ } else { - error_log('XML-RPC: xmlrpc_server::service: http headers already sent before response is fully generated. Check for php warning or error messages'); + error_log('XML-RPC: '.__METHOD__.': http headers already sent before response is fully generated. Check for php warning or error messages'); } print $payload; @@ -704,7 +712,7 @@ } if ($sigdoc) { - $this->dmap[$methodname]['signature_docs'] = $sigdoc; + $this->dmap[$methodname]['signature_docs'] = $sigdoc; } } @@ -782,10 +790,11 @@ */ function parseRequestHeaders(&$data, &$req_encoding, &$resp_encoding, &$resp_compression) { - // Play nice to PHP 4.0.x: superglobals were not yet invented... - if(!isset($_SERVER)) + // check if $_SERVER is populated: it might have been disabled via ini file + // (this is true even when in CLI mode) + if (count($_SERVER) == 0) { - $_SERVER = $GLOBALS['HTTP_SERVER_VARS']; + error_log('XML-RPC: '.__METHOD__.': cannot parse request headers as $_SERVER is not populated'); } if($this->debug > 1) @@ -934,7 +943,7 @@ // 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); + error_log('XML-RPC: '.__METHOD__.': 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, @@ -989,10 +998,10 @@ else { xml_parser_free($parser); - // small layering violation in favor of speed and memory usage: - // we should allow the 'execute' method handle this, but in the - // most common scenario (xmlrpcvals type server with some methods - // registered as phpvals) that would mean a useless encode+decode pass + // small layering violation in favor of speed and memory usage: + // we should allow the 'execute' method handle this, but in the + // most common scenario (xmlrpcvals type server with some methods + // registered as phpvals) that would mean a useless encode+decode pass if ($this->functions_parameters_type != 'xmlrpcvals' || (isset($this->dmap[$GLOBALS['_xh']['method']]['parameters_type']) && ($this->dmap[$GLOBALS['_xh']['method']]['parameters_type'] == 'phpvals'))) { if($this->debug > 1) @@ -1082,7 +1091,7 @@ // verify that function to be invoked is in fact callable if(!is_callable($func)) { - error_log("XML-RPC: xmlrpc_server::execute: function $func registered as method handler is not callable"); + error_log("XML-RPC: ".__METHOD__.": function $func registered as method handler is not callable"); return new xmlrpcresp( 0, $GLOBALS['xmlrpcerr']['server_error'], @@ -1096,72 +1105,91 @@ { $GLOBALS['_xmlrpcs_prev_ehandler'] = set_error_handler('_xmlrpcs_errorHandler'); } - // Allow mixed-convention servers - if (is_object($m)) + try { - if($sysCall) - { - $r = call_user_func($func, $this, $m); - } - else - { - $r = call_user_func($func, $m); - } - if (!is_a($r, 'xmlrpcresp')) + // Allow mixed-convention servers + if (is_object($m)) { - error_log("XML-RPC: xmlrpc_server::execute: function $func registered as method handler does not return an xmlrpcresp object"); - if (is_a($r, 'xmlrpcval')) + if($sysCall) { - $r = new xmlrpcresp($r); + $r = call_user_func($func, $this, $m); } else { - $r = new xmlrpcresp( - 0, - $GLOBALS['xmlrpcerr']['server_error'], - $GLOBALS['xmlrpcstr']['server_error'] . ": function does not return xmlrpcresp object" - ); + $r = call_user_func($func, $m); + } + if (!is_a($r, 'xmlrpcresp')) + { + error_log("XML-RPC: ".__METHOD__.": function $func registered as method handler does not return an xmlrpcresp object"); + if (is_a($r, 'xmlrpcval')) + { + $r = new xmlrpcresp($r); + } + else + { + $r = new xmlrpcresp( + 0, + $GLOBALS['xmlrpcerr']['server_error'], + $GLOBALS['xmlrpcstr']['server_error'] . ": function does not return xmlrpcresp object" + ); + } } - } - } - else - { - // call a 'plain php' function - if($sysCall) - { - array_unshift($params, $this); - $r = call_user_func_array($func, $params); } else { - // 3rd API convention for method-handling functions: EPI-style - if ($this->functions_parameters_type == 'epivals') + // call a 'plain php' function + if($sysCall) + { + array_unshift($params, $this); + $r = call_user_func_array($func, $params); + } + else { - $r = call_user_func_array($func, array($methName, $params, $this->user_data)); - // mimic EPI behaviour: if we get an array that looks like an error, make it - // an eror response - if (is_array($r) && array_key_exists('faultCode', $r) && array_key_exists('faultString', $r)) + // 3rd API convention for method-handling functions: EPI-style + if ($this->functions_parameters_type == 'epivals') { - $r = new xmlrpcresp(0, (integer)$r['faultCode'], (string)$r['faultString']); + $r = call_user_func_array($func, array($methName, $params, $this->user_data)); + // mimic EPI behaviour: if we get an array that looks like an error, make it + // an eror response + if (is_array($r) && array_key_exists('faultCode', $r) && array_key_exists('faultString', $r)) + { + $r = new xmlrpcresp(0, (integer)$r['faultCode'], (string)$r['faultString']); + } + else + { + // functions using EPI api should NOT return resp objects, + // so make sure we encode the return type correctly + $r = new xmlrpcresp(php_xmlrpc_encode($r, array('extension_api'))); + } } else { - // functions using EPI api should NOT return resp objects, - // so make sure we encode the return type correctly - $r = new xmlrpcresp(php_xmlrpc_encode($r, array('extension_api'))); + $r = call_user_func_array($func, $params); } } - else + // the return type can be either an xmlrpcresp object or a plain php value... + if (!is_a($r, 'xmlrpcresp')) { - $r = call_user_func_array($func, $params); + // what should we assume here about automatic encoding of datetimes + // and php classes instances??? + $r = new xmlrpcresp(php_xmlrpc_encode($r, $this->phpvals_encoding_options)); } } - // the return type can be either an xmlrpcresp object or a plain php value... - if (!is_a($r, 'xmlrpcresp')) + } + catch(Exception $e) + { + // (barring errors in the lib) an uncatched exception happened + // in the called function, we wrap it in a proper error-response + switch($this->exception_handling) { - // what should we assume here about automatic encoding of datetimes - // and php classes instances??? - $r = new xmlrpcresp(php_xmlrpc_encode($r, $this->phpvals_encoding_options)); + case 2: + throw $e; + break; + case 1: + $r = new xmlrpcresp(0, $e->getCode(), $e->getMessage()); + break; + default: + $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['server_error'], $GLOBALS['xmlrpcstr']['server_error']); } } if($this->debug > 2)