From: gggeek Date: Sun, 15 Jan 2023 23:20:39 +0000 (+0000) Subject: fix mixed-calling-convention servers; prefer return-early; reformat comments X-Git-Tag: 4.10.0~159 X-Git-Url: http://git.onelab.eu/?a=commitdiff_plain;h=7cdbccc7b87b55e326fe3560fb512f1b61491336;p=plcapi.git fix mixed-calling-convention servers; prefer return-early; reformat comments --- diff --git a/demo/server/methodProviders/testsuite.php b/demo/server/methodProviders/testsuite.php index 45c26dbb..2c6f2f16 100644 --- a/demo/server/methodProviders/testsuite.php +++ b/demo/server/methodProviders/testsuite.php @@ -35,18 +35,20 @@ function getAllHeaders_xmlrpc($req) } } +// used to test mixed-convention calling $setcookies_sig = array(array(Value::$xmlrpcInt, Value::$xmlrpcStruct)); $setcookies_doc = 'Sends to client a response containing a single \'1\' digit, and sets to it http cookies as received in the request (array of structs describing a cookie)'; -function setCookies($req) +function setCookies($cookies) { - $encoder = new Encoder(); - $cookies = $req->getParam(0); - foreach ($cookies as $name => $value) { - $cookieDesc = $encoder->decode($value); - setcookie($name, @$cookieDesc['value'], @$cookieDesc['expires'], @$cookieDesc['path'], @$cookieDesc['domain'], @$cookieDesc['secure']); + foreach ($cookies as $name => $cookieDesc) { + if (is_array($cookieDesc)) { + setcookie($name, @$cookieDesc['value'], @$cookieDesc['expires'], @$cookieDesc['path'], @$cookieDesc['domain'], @$cookieDesc['secure']); + } else { + /// @todo + } } - return new Response(new Value(1, Value::$xmlrpcInt)); + return 1; } $getcookies_sig = array(array(Value::$xmlrpcStruct)); @@ -83,6 +85,7 @@ return array( "function" => 'setCookies', "signature" => $setcookies_sig, "docstring" => $setcookies_doc, + "parameters_type" => 'phpvals', ), "tests.getcookies" => array( "function" => 'getCookies', diff --git a/src/Helper/XMLParser.php b/src/Helper/XMLParser.php index 6da8ef9a..b6f3c041 100644 --- a/src/Helper/XMLParser.php +++ b/src/Helper/XMLParser.php @@ -11,10 +11,12 @@ use PhpXmlRpc\Value; * * @todo implement an interface to allow for alternative implementations * - make access to $_xh protected, return more high-level data structures + * - move $this->accept, $this->callbacks to an internal-use parsing-options config, along with the private + * parts of $_xh * - add parseRequest, parseResponse, parseValue methods * @todo if iconv() or mb_string() are available, we could allow to convert the received xml to a custom charset encoding * while parsing, which is faster than doing it later by going over the rebuilt data structure - * @todo allow usage of a custom Logger via the DIC(ish) pattern we use in other classes + * @todo allow to parse data from a stream, to avoid having to copy first the whole xml to memory */ class XMLParser { @@ -29,21 +31,26 @@ class XMLParser protected static $logger; - // Used to store state during parsing and to pass parsing results to callers. - // Quick explanation of components: - // private: - // ac - used to accumulate values - // stack - array with genealogy of xml elements names used to validate nesting of xmlrpc elements - // valuestack - array used for parsing arrays and structs - // lv - used to indicate "looking for a value": implements the logic to allow values with no types to be strings - // public: - // isf - used to indicate an xml parsing fault (3), invalid xmlrpc fault (2) or xmlrpc response fault (1) - // isf_reason - used for storing xmlrpc response fault string - // value - used to store the value in responses - // method - used to store method name in requests - // params - used to store parameters in requests - // pt - used to store the type of each received parameter. Useful if parameters are automatically decoded to php values - // rt - 'methodcall', 'methodresponse', 'value' or 'fault' (the last one used only in EPI emulation mode) + /** + * @var array + * Used to store state during parsing and to pass parsing results to callers. + * Quick explanation of components: + * private: + * ac - used to accumulate values + * stack - array with genealogy of xml elements names, used to validate nesting of xmlrpc elements + * valuestack - array used for parsing arrays and structs + * lv - used to indicate "looking for a value": implements the logic to allow values with no types to be strings + * (values: 0=not looking, 1=looking, 3=found) + * public: + * isf - used to indicate an xml-rpc response fault (1), invalid xml-rpc fault (2), xml parsing fault (3) or + * bad parameters passed to the parsing call (4) + * isf_reason - used for storing xml-rpc response fault string + * value - used to store the value in responses + * method - used to store method name in requests + * params - used to store parameters in requests + * pt - used to store the type of each received parameter. Useful if parameters are automatically decoded to php values + * rt - 'methodcall', 'methodresponse', 'value' or 'fault' (the last one used only in EPI emulation mode) + */ public $_xh = array( 'ac' => '', 'stack' => array(), @@ -57,6 +64,10 @@ class XMLParser 'rt' => '', ); + /** + * @var array[] + * @internal + */ public $xmlrpc_valid_parents = array( 'VALUE' => array('MEMBER', 'DATA', 'PARAM', 'FAULT'), 'BOOLEAN' => array('VALUE'), @@ -81,12 +92,14 @@ class XMLParser 'EX:NIL' => array('VALUE'), // only used when extension activated ); - /** @var array $parsing_options */ + /** @var int[] $parsing_options */ protected $parsing_options = array(); /** @var int $accept self::ACCEPT_REQUEST | self::ACCEPT_RESPONSE by default */ protected $accept = 3; /** @var int $maxChunkLength 4 MB by default. Any value below 10MB should be good */ protected $maxChunkLength = 4194304; + /** @var \Callable[] */ + protected $callbacks = array(); public function getLogger() { @@ -106,7 +119,7 @@ class XMLParser } /** - * @param array $options passed to the xml parser + * @param int[] $options passed to the xml parser */ public function __construct(array $options = array()) { @@ -115,9 +128,10 @@ class XMLParser /** * @param string $data - * @param string $returnType + * @param string $returnType self::RETURN_XMLRPCVALS, self::RETURN_PHP, self::RETURN_EPIVALS * @param int $accept a bit-combination of self::ACCEPT_REQUEST, self::ACCEPT_RESPONSE, self::ACCEPT_VALUE - * @param array $options passed to the xml parser, in addition to the options received in the constructor + * @param array $options integer-key options are passed to the xml parser, in addition to the options received in + * the constructor. String-key options are used independently * @return void */ public function parse($data, $returnType = self::RETURN_XMLRPCVALS, $accept = 3, $options = array()) @@ -144,6 +158,29 @@ class XMLParser return; } + $prevAccept = $this->accept; + $this->accept = $accept; + + $this->callbacks = array(); + foreach ($options as $key => $val) { + if (is_string($key)) { + switch($key) { + case 'methodname_callback': + if (!is_callable($val)) { + $this->_xh['isf'] = 4; + $this->_xh['isf_reason'] = "Callback passed as 'methodname_callback' is not callable"; + return; + } else { + $this->callbacks['methodname'] = $val; + } + break; + default: + $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ": unsupported option: $key"); + } + unset($options[$key]); + } + } + // NB: we use '' instead of null to force charset detection from the xml declaration $parser = xml_parser_create(''); @@ -165,6 +202,8 @@ class XMLParser case self::RETURN_EPIVALS: xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_epi'); break; + /// @todo log a warning on unsupported return type + case XMLParser::RETURN_XMLRPCVALS: default: xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee'); } @@ -172,24 +211,36 @@ class XMLParser xml_set_character_data_handler($parser, 'xmlrpc_cd'); xml_set_default_handler($parser, 'xmlrpc_dh'); - $this->accept = $accept; - - // @see ticket #70 - we have to parse big xml docks in chunks to avoid errors - for ($offset = 0; $offset < $len; $offset += $this->maxChunkLength) { - $chunk = substr($data, $offset, $this->maxChunkLength); - // error handling: xml not well formed - if (!xml_parse($parser, $chunk, $offset + $this->maxChunkLength >= $len)) { - $errCode = xml_get_error_code($parser); - $errStr = sprintf('XML error %s: %s at line %d, column %d', $errCode, xml_error_string($errCode), - xml_get_current_line_number($parser), xml_get_current_column_number($parser)); - - $this->_xh['isf'] = 3; - $this->_xh['isf_reason'] = $errStr; - break; + try { + // @see ticket #70 - we have to parse big xml docs in chunks to avoid errors + for ($offset = 0; $offset < $len; $offset += $this->maxChunkLength) { + $chunk = substr($data, $offset, $this->maxChunkLength); + // error handling: xml not well formed + if (!xml_parse($parser, $chunk, $offset + $this->maxChunkLength >= $len)) { + $errCode = xml_get_error_code($parser); + $errStr = sprintf('XML error %s: %s at line %d, column %d', $errCode, xml_error_string($errCode), + xml_get_current_line_number($parser), xml_get_current_column_number($parser)); + + $this->_xh['isf'] = 3; + $this->_xh['isf_reason'] = $errStr; + break; + } + // no need to parse further if we already have a fatal error + if ($this->_xh['isf'] >= 2) { + break; + } } + } catch (\Exception $e) { + xml_parser_free($parser); + $this->callbacks = array(); + $this->accept = $prevAccept; + /// @todo should we set $this->_xh['isf'] and $this->_xh['isf_reason'] ? + throw $e; } xml_parser_free($parser); + $this->callbacks = array(); + $this->accept = $prevAccept; } /** @@ -201,82 +252,148 @@ class XMLParser * @param $attrs * @param bool $acceptSingleVals DEPRECATED use the $accept parameter instead * @return void + * + * @todo optimization: throw when setting $this->_xh['isf'] > 1, to completely avoid further xml parsing */ public function xmlrpc_se($parser, $name, $attrs, $acceptSingleVals = false) { // if invalid xmlrpc already detected, skip all processing - if ($this->_xh['isf'] < 2) { - - // check for correct element nesting - if (count($this->_xh['stack']) == 0) { - // top level element can only be of 2 types - /// @todo optimization creep: save this check into a bool variable, instead of using count() every time: - /// there is only a single top level element in xml anyway - // BC - if ($acceptSingleVals === false) { - $accept = $this->accept; - } else { - $accept = self::ACCEPT_REQUEST | self::ACCEPT_RESPONSE | self::ACCEPT_VALUE; + if ($this->_xh['isf'] >= 2) { + return; + } + + // check for correct element nesting + if (count($this->_xh['stack']) == 0) { + // top level element can only be of 2 types + /// @todo optimization creep: save this check into a bool variable, instead of using count() every time: + /// there is only a single top level element in xml anyway + // BC + if ($acceptSingleVals === false) { + $accept = $this->accept; + } else { + $accept = self::ACCEPT_REQUEST | self::ACCEPT_RESPONSE | self::ACCEPT_VALUE; + } + if (($name == 'METHODCALL' && ($accept & self::ACCEPT_REQUEST)) || + ($name == 'METHODRESPONSE' && ($accept & self::ACCEPT_RESPONSE)) || + ($name == 'VALUE' && ($accept & self::ACCEPT_VALUE)) || + ($name == 'FAULT' && ($accept & self::ACCEPT_FAULT))) { + $this->_xh['rt'] = strtolower($name); + } else { + $this->_xh['isf'] = 2; + $this->_xh['isf_reason'] = 'missing top level xmlrpc element. Found: ' . $name; + + return; + } + } else { + // not top level element: see if parent is OK + $parent = end($this->_xh['stack']); + if (!array_key_exists($name, $this->xmlrpc_valid_parents) || !in_array($parent, $this->xmlrpc_valid_parents[$name])) { + $this->_xh['isf'] = 2; + $this->_xh['isf_reason'] = "xmlrpc element $name cannot be child of $parent"; + + return; + } + } + + switch ($name) { + // optimize for speed switch cases: most common cases first + case 'VALUE': + /// @todo we could check for 2 VALUE elements inside a MEMBER or PARAM element + $this->_xh['vt'] = 'value'; // indicator: no value found yet + $this->_xh['ac'] = ''; + $this->_xh['lv'] = 1; + $this->_xh['php_class'] = null; + break; + + case 'I8': + case 'EX:I8': + if (PHP_INT_SIZE === 4) { + // INVALID ELEMENT: RAISE ISF so that it is later recognized!!! + $this->_xh['isf'] = 2; + $this->_xh['isf_reason'] = "Received i8 element but php is compiled in 32 bit mode"; + + return; } - if (($name == 'METHODCALL' && ($accept & self::ACCEPT_REQUEST)) || - ($name == 'METHODRESPONSE' && ($accept & self::ACCEPT_RESPONSE)) || - ($name == 'VALUE' && ($accept & self::ACCEPT_VALUE)) || - ($name == 'FAULT' && ($accept & self::ACCEPT_FAULT))) { - $this->_xh['rt'] = strtolower($name); - } else { + // fall through voluntarily + + case 'I4': + case 'INT': + case 'STRING': + case 'BOOLEAN': + case 'DOUBLE': + case 'DATETIME.ISO8601': + case 'BASE64': + if ($this->_xh['vt'] != 'value') { + // two data elements inside a value: an error occurred! $this->_xh['isf'] = 2; - $this->_xh['isf_reason'] = 'missing top level xmlrpc element. Found: ' . $name; + $this->_xh['isf_reason'] = "$name element following a {$this->_xh['vt']} element inside a single value"; return; } - } else { - // not top level element: see if parent is OK - $parent = end($this->_xh['stack']); - if (!array_key_exists($name, $this->xmlrpc_valid_parents) || !in_array($parent, $this->xmlrpc_valid_parents[$name])) { + $this->_xh['ac'] = ''; // reset the accumulator + break; + + case 'STRUCT': + case 'ARRAY': + if ($this->_xh['vt'] != 'value') { + // two data elements inside a value: an error occurred! $this->_xh['isf'] = 2; - $this->_xh['isf_reason'] = "xmlrpc element $name cannot be child of $parent"; + $this->_xh['isf_reason'] = "$name element following a {$this->_xh['vt']} element inside a single value"; return; } - } + // create an empty array to hold child values, and push it onto appropriate stack + $curVal = array(); + $curVal['values'] = array(); + $curVal['type'] = $name; + // check for out-of-band information to rebuild php objs + // and in case it is found, save it + if (@isset($attrs['PHP_CLASS'])) { + $curVal['php_class'] = $attrs['PHP_CLASS']; + } + $this->_xh['valuestack'][] = $curVal; + $this->_xh['vt'] = 'data'; // be prepared for a data element next + break; - switch ($name) { - // optimize for speed switch cases: most common cases first - case 'VALUE': - /// @todo we could check for 2 VALUE elements inside a MEMBER or PARAM element - $this->_xh['vt'] = 'value'; // indicator: no value found yet - $this->_xh['ac'] = ''; - $this->_xh['lv'] = 1; - $this->_xh['php_class'] = null; - break; - case 'I8': - case 'EX:I8': - if (PHP_INT_SIZE === 4) { - // INVALID ELEMENT: RAISE ISF so that it is later recognized!!! - $this->_xh['isf'] = 2; - $this->_xh['isf_reason'] = "Received i8 element but php is compiled in 32 bit mode"; + case 'DATA': + if ($this->_xh['vt'] != 'data') { + // two data elements inside a value: an error occurred! + $this->_xh['isf'] = 2; + $this->_xh['isf_reason'] = "found two data elements inside an array element"; - return; - } - // fall through voluntarily - case 'I4': - case 'INT': - case 'STRING': - case 'BOOLEAN': - case 'DOUBLE': - case 'DATETIME.ISO8601': - case 'BASE64': - if ($this->_xh['vt'] != 'value') { - // two data elements inside a value: an error occurred! - $this->_xh['isf'] = 2; - $this->_xh['isf_reason'] = "$name element following a {$this->_xh['vt']} element inside a single value"; + return; + } - return; - } - $this->_xh['ac'] = ''; // reset the accumulator - break; - case 'STRUCT': - case 'ARRAY': + case 'METHODCALL': + case 'METHODRESPONSE': + case 'PARAMS': + // valid elements that add little to processing + break; + + case 'METHODNAME': + case 'NAME': + /// @todo we could check for 2 NAME elements inside a MEMBER element + $this->_xh['ac'] = ''; + break; + + case 'FAULT': + $this->_xh['isf'] = 1; + break; + + case 'MEMBER': + // set member name to null, in case we do not find in the xml later on + $this->_xh['valuestack'][count($this->_xh['valuestack']) - 1]['name'] = ''; + //$this->_xh['ac']=''; + // Drop trough intentionally + + case 'PARAM': + // clear value type, so we can check later if no value has been passed for this param/member + $this->_xh['vt'] = null; + break; + + case 'NIL': + case 'EX:NIL': + if (PhpXmlRpc::$xmlrpc_null_extension) { if ($this->_xh['vt'] != 'value') { // two data elements inside a value: an error occurred! $this->_xh['isf'] = 2; @@ -284,77 +401,27 @@ class XMLParser return; } - // create an empty array to hold child values, and push it onto appropriate stack - $curVal = array(); - $curVal['values'] = array(); - $curVal['type'] = $name; - // check for out-of-band information to rebuild php objs - // and in case it is found, save it - if (@isset($attrs['PHP_CLASS'])) { - $curVal['php_class'] = $attrs['PHP_CLASS']; - } - $this->_xh['valuestack'][] = $curVal; - $this->_xh['vt'] = 'data'; // be prepared for a data element next - break; - case 'DATA': - if ($this->_xh['vt'] != 'data') { - // two data elements inside a value: an error occurred! - $this->_xh['isf'] = 2; - $this->_xh['isf_reason'] = "found two data elements inside an array element"; - - return; - } - case 'METHODCALL': - case 'METHODRESPONSE': - case 'PARAMS': - // valid elements that add little to processing - break; - case 'METHODNAME': - case 'NAME': - /// @todo we could check for 2 NAME elements inside a MEMBER element + // reset the accumulator - q: is this necessary at all here? $this->_xh['ac'] = ''; break; - case 'FAULT': - $this->_xh['isf'] = 1; - break; - case 'MEMBER': - // set member name to null, in case we do not find in the xml later on - $this->_xh['valuestack'][count($this->_xh['valuestack']) - 1]['name'] = ''; - //$this->_xh['ac']=''; - // Drop trough intentionally - case 'PARAM': - // clear value type, so we can check later if no value has been passed for this param/member - $this->_xh['vt'] = null; - break; - case 'NIL': - case 'EX:NIL': - if (PhpXmlRpc::$xmlrpc_null_extension) { - if ($this->_xh['vt'] != 'value') { - // two data elements inside a value: an error occurred! - $this->_xh['isf'] = 2; - $this->_xh['isf_reason'] = "$name element following a {$this->_xh['vt']} element inside a single value"; - - return; - } - $this->_xh['ac'] = ''; // reset the accumulator - break; - } + } // if here, we do not support the extension, so // drop through intentionally - default: - // INVALID ELEMENT: RAISE ISF so that it is later recognized!!! - $this->_xh['isf'] = 2; - $this->_xh['isf_reason'] = "found not-xmlrpc xml element $name"; - break; - } - // Save current element name to stack, to validate nesting - $this->_xh['stack'][] = $name; + default: + // INVALID ELEMENT: RAISE ISF so that it is later recognized + /// @todo feature creep = allow a callback instead + $this->_xh['isf'] = 2; + $this->_xh['isf_reason'] = "found not-xmlrpc xml element $name"; + break; + } - /// @todo optimization creep: move this inside the big switch() above - if ($name != 'VALUE') { - $this->_xh['lv'] = 0; - } + // Save current element name to stack, to validate nesting + $this->_xh['stack'][] = $name; + + /// @todo optimization creep: move this inside the big switch() above + if ($name != 'VALUE') { + $this->_xh['lv'] = 0; } } @@ -383,181 +450,193 @@ class XMLParser */ public function xmlrpc_ee($parser, $name, $rebuildXmlrpcvals = 1) { - if ($this->_xh['isf'] < 2) { - // push this element name from stack - // NB: if XML validates, correct opening/closing is guaranteed and - // we do not have to check for $name == $currElem. - // we also checked for proper nesting at start of elements... - $currElem = array_pop($this->_xh['stack']); - - switch ($name) { - case 'VALUE': - // This if() detects if no scalar was inside - if ($this->_xh['vt'] == 'value') { - $this->_xh['value'] = $this->_xh['ac']; - $this->_xh['vt'] = Value::$xmlrpcString; - } + if ($this->_xh['isf'] >= 2) { + return; - if ($rebuildXmlrpcvals > 0) { - // build the xmlrpc val out of the data received, and substitute it - $temp = new Value($this->_xh['value'], $this->_xh['vt']); - // in case we got info about underlying php class, save it - // in the object we're rebuilding - if (isset($this->_xh['php_class'])) { - $temp->_php_class = $this->_xh['php_class']; - } - $this->_xh['value'] = $temp; - } elseif ($rebuildXmlrpcvals < 0) { - if ($this->_xh['vt'] == Value::$xmlrpcDateTime) { - $this->_xh['value'] = (object)array( - 'xmlrpc_type' => 'datetime', - 'scalar' => $this->_xh['value'], - 'timestamp' => \PhpXmlRpc\Helper\Date::iso8601Decode($this->_xh['value']) - ); - } elseif ($this->_xh['vt'] == Value::$xmlrpcBase64) { - $this->_xh['value'] = (object)array( - 'xmlrpc_type' => 'base64', - 'scalar' => $this->_xh['value'] - ); - } - } else { - /// @todo this should handle php-serialized objects, - /// since std deserializing is done by php_xmlrpc_decode, - /// which we will not be calling... - //if (isset($this->_xh['php_class'])) { - //} + } + // push this element name from stack + // NB: if XML validates, correct opening/closing is guaranteed and we do not have to check for $name == $currElem. + // we also checked for proper nesting at start of elements... + $currElem = array_pop($this->_xh['stack']); + + switch ($name) { + case 'VALUE': + // This if() detects if no scalar was inside + if ($this->_xh['vt'] == 'value') { + $this->_xh['value'] = $this->_xh['ac']; + $this->_xh['vt'] = Value::$xmlrpcString; + } + + if ($rebuildXmlrpcvals > 0) { + // build the xmlrpc val out of the data received, and substitute it + $temp = new Value($this->_xh['value'], $this->_xh['vt']); + // in case we got info about underlying php class, save it in the object we're rebuilding + if (isset($this->_xh['php_class'])) { + $temp->_php_class = $this->_xh['php_class']; + } + $this->_xh['value'] = $temp; + } elseif ($rebuildXmlrpcvals < 0) { + if ($this->_xh['vt'] == Value::$xmlrpcDateTime) { + $this->_xh['value'] = (object)array( + 'xmlrpc_type' => 'datetime', + 'scalar' => $this->_xh['value'], + 'timestamp' => \PhpXmlRpc\Helper\Date::iso8601Decode($this->_xh['value']) + ); + } elseif ($this->_xh['vt'] == Value::$xmlrpcBase64) { + $this->_xh['value'] = (object)array( + 'xmlrpc_type' => 'base64', + 'scalar' => $this->_xh['value'] + ); } + } else { + /// @todo this should handle php-serialized objects, since std deserializing is done + /// by php_xmlrpc_decode, which we will not be calling... + //if (isset($this->_xh['php_class'])) { + //} + } - // check if we are inside an array or struct: - // if value just built is inside an array, let's move it into array on the stack - $vscount = count($this->_xh['valuestack']); - if ($vscount && $this->_xh['valuestack'][$vscount - 1]['type'] == 'ARRAY') { - $this->_xh['valuestack'][$vscount - 1]['values'][] = $this->_xh['value']; + // check if we are inside an array or struct: + // if value just built is inside an array, let's move it into array on the stack + $vscount = count($this->_xh['valuestack']); + if ($vscount && $this->_xh['valuestack'][$vscount - 1]['type'] == 'ARRAY') { + $this->_xh['valuestack'][$vscount - 1]['values'][] = $this->_xh['value']; + } + break; + + case 'BOOLEAN': + case 'I4': + case 'I8': + case 'EX:I8': + case 'INT': + case 'STRING': + case 'DOUBLE': + case 'DATETIME.ISO8601': + case 'BASE64': + $this->_xh['vt'] = strtolower($name); + /// @todo: optimization creep - remove the if/elseif cycle below + /// since the case() in which we are already did that + if ($name == 'STRING') { + $this->_xh['value'] = $this->_xh['ac']; + } elseif ($name == 'DATETIME.ISO8601') { + if (!preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $this->_xh['ac'])) { + $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': invalid value received in DATETIME: ' . $this->_xh['ac']); } - break; - case 'BOOLEAN': - case 'I4': - case 'I8': - case 'EX:I8': - case 'INT': - case 'STRING': - case 'DOUBLE': - case 'DATETIME.ISO8601': - case 'BASE64': - $this->_xh['vt'] = strtolower($name); - /// @todo: optimization creep - remove the if/elseif cycle below - /// since the case() in which we are already did that - if ($name == 'STRING') { - $this->_xh['value'] = $this->_xh['ac']; - } elseif ($name == 'DATETIME.ISO8601') { - if (!preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $this->_xh['ac'])) { - $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': invalid value received in DATETIME: ' . $this->_xh['ac']); - } - $this->_xh['vt'] = Value::$xmlrpcDateTime; - $this->_xh['value'] = $this->_xh['ac']; - } elseif ($name == 'BASE64') { - /// @todo check for failure of base64 decoding / catch warnings - $this->_xh['value'] = base64_decode($this->_xh['ac']); - } elseif ($name == 'BOOLEAN') { - // special case here: we translate boolean 1 or 0 into PHP - // constants true or false. - // Strings 'true' and 'false' are accepted, even though the - // spec never mentions them (see eg. Blogger api docs) - // NB: this simple checks helps a lot sanitizing input, ie no - // security problems around here - if ($this->_xh['ac'] == '1' || strcasecmp($this->_xh['ac'], 'true') == 0) { - $this->_xh['value'] = true; - } else { - // log if receiving something strange, even though we set the value to false anyway - if ($this->_xh['ac'] != '0' && strcasecmp($this->_xh['ac'], 'false') != 0) { - $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': invalid value received in BOOLEAN: ' . $this->_xh['ac']); - } - $this->_xh['value'] = false; - } - } elseif ($name == 'DOUBLE') { - // we have a DOUBLE - // we must check that only 0123456789-. are characters here - // NOTE: regexp could be much stricter than this... - if (!preg_match('/^[+-eE0123456789 \t.]+$/', $this->_xh['ac'])) { - /// @todo: find a better way of throwing an error than this! - $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': non numeric value received in DOUBLE: ' . $this->_xh['ac']); - $this->_xh['value'] = 'ERROR_NON_NUMERIC_FOUND'; - } else { - // it's ok, add it on - $this->_xh['value'] = (double)$this->_xh['ac']; - } + $this->_xh['vt'] = Value::$xmlrpcDateTime; + $this->_xh['value'] = $this->_xh['ac']; + } elseif ($name == 'BASE64') { + /// @todo check for failure of base64 decoding / catch warnings + $this->_xh['value'] = base64_decode($this->_xh['ac']); + } elseif ($name == 'BOOLEAN') { + // special case here: we translate boolean 1 or 0 into PHP constants true or false. + // Strings 'true' and 'false' are accepted, even though the spec never mentions them (see eg. + // Blogger api docs) + // NB: this simple checks helps a lot sanitizing input, ie. no security problems around here + if ($this->_xh['ac'] == '1' || strcasecmp($this->_xh['ac'], 'true') == 0) { + $this->_xh['value'] = true; } else { - // we have an I4/I8/INT - // we must check that only 0123456789- are characters here - if (!preg_match('/^[+-]?[0123456789 \t]+$/', $this->_xh['ac'])) { - /// @todo find a better way of throwing an error than this! - $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': non numeric value received in INT: ' . $this->_xh['ac']); - $this->_xh['value'] = 'ERROR_NON_NUMERIC_FOUND'; - } else { - // it's ok, add it on - $this->_xh['value'] = (int)$this->_xh['ac']; + // log if receiving something strange, even though we set the value to false anyway + if ($this->_xh['ac'] != '0' && strcasecmp($this->_xh['ac'], 'false') != 0) { + $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': invalid value received in BOOLEAN: ' . $this->_xh['ac']); } + $this->_xh['value'] = false; } - $this->_xh['lv'] = 3; // indicate we've found a value - break; - case 'NAME': - $this->_xh['valuestack'][count($this->_xh['valuestack']) - 1]['name'] = $this->_xh['ac']; - break; - case 'MEMBER': - // add to array in the stack the last element built, - // unless no VALUE was found - if ($this->_xh['vt']) { - $vscount = count($this->_xh['valuestack']); - $this->_xh['valuestack'][$vscount - 1]['values'][$this->_xh['valuestack'][$vscount - 1]['name']] = $this->_xh['value']; + } elseif ($name == 'DOUBLE') { + // we have a DOUBLE + // we must check that only 0123456789-. are characters here + // NOTE: regexp could be much stricter than this... + if (!preg_match('/^[+-eE0123456789 \t.]+$/', $this->_xh['ac'])) { + /// @todo: find a better way of throwing an error than this! + $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': non numeric value received in DOUBLE: ' . $this->_xh['ac']); + $this->_xh['value'] = 'ERROR_NON_NUMERIC_FOUND'; } else { - $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': missing VALUE inside STRUCT in received xml'); - } - break; - case 'DATA': - $this->_xh['vt'] = null; // reset this to check for 2 data elements in a row - even if they're empty - break; - case 'STRUCT': - case 'ARRAY': - // fetch out of stack array of values, and promote it to current value - $currVal = array_pop($this->_xh['valuestack']); - $this->_xh['value'] = $currVal['values']; - $this->_xh['vt'] = strtolower($name); - if (isset($currVal['php_class'])) { - $this->_xh['php_class'] = $currVal['php_class']; + // it's ok, add it on + $this->_xh['value'] = (double)$this->_xh['ac']; } - break; - case 'PARAM': - // add to array of params the current value, - // unless no VALUE was found - if ($this->_xh['vt']) { - $this->_xh['params'][] = $this->_xh['value']; - $this->_xh['pt'][] = $this->_xh['vt']; + } else { + // we have an I4/I8/INT + // we must check that only 0123456789- are characters here + if (!preg_match('/^[+-]?[0123456789 \t]+$/', $this->_xh['ac'])) { + /// @todo find a better way of throwing an error than this! + $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': non numeric value received in INT: ' . $this->_xh['ac']); + $this->_xh['value'] = 'ERROR_NON_NUMERIC_FOUND'; } else { - $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': missing VALUE inside PARAM in received xml'); - } - break; - case 'METHODNAME': - $this->_xh['method'] = preg_replace('/^[\n\r\t ]+/', '', $this->_xh['ac']); - break; - case 'NIL': - case 'EX:NIL': - if (PhpXmlRpc::$xmlrpc_null_extension) { - $this->_xh['vt'] = 'null'; - $this->_xh['value'] = null; - $this->_xh['lv'] = 3; - break; + // it's ok, add it on + $this->_xh['value'] = (int)$this->_xh['ac']; } - // drop through intentionally if nil extension not enabled - case 'PARAMS': - case 'FAULT': - case 'METHODCALL': - case 'METHORESPONSE': - break; - default: - // End of INVALID ELEMENT! - // shall we add an assert here for unreachable code??? + } + $this->_xh['lv'] = 3; // indicate we've found a value + break; + + case 'NAME': + $this->_xh['valuestack'][count($this->_xh['valuestack']) - 1]['name'] = $this->_xh['ac']; + break; + + case 'MEMBER': + // add to array in the stack the last element built, unless no VALUE was found + if ($this->_xh['vt']) { + $vscount = count($this->_xh['valuestack']); + $this->_xh['valuestack'][$vscount - 1]['values'][$this->_xh['valuestack'][$vscount - 1]['name']] = $this->_xh['value']; + } else { + $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': missing VALUE inside STRUCT in received xml'); + } + break; + + case 'DATA': + $this->_xh['vt'] = null; // reset this to check for 2 data elements in a row - even if they're empty + break; + + case 'STRUCT': + case 'ARRAY': + // fetch out of stack array of values, and promote it to current value + $currVal = array_pop($this->_xh['valuestack']); + $this->_xh['value'] = $currVal['values']; + $this->_xh['vt'] = strtolower($name); + if (isset($currVal['php_class'])) { + $this->_xh['php_class'] = $currVal['php_class']; + } + break; + + case 'PARAM': + // add to array of params the current value, unless no VALUE was found + if ($this->_xh['vt']) { + $this->_xh['params'][] = $this->_xh['value']; + $this->_xh['pt'][] = $this->_xh['vt']; + } else { + $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': missing VALUE inside PARAM in received xml'); + } + break; + + case 'METHODNAME': + /// @todo why do we strip leading whitespace in method names, but not trailing whitespace? + $methodname = preg_replace('/^[\n\r\t ]+/', '', $this->_xh['ac']); + $this->_xh['method'] = $methodname; + // we allow the callback to f.e. give us back a mangled method name by manipulating $this + if (isset($this->callbacks['methodname'])) { + call_user_func($this->callbacks['methodname'], $methodname, $this, $parser); + } + break; + + case 'NIL': + case 'EX:NIL': + if (PhpXmlRpc::$xmlrpc_null_extension) { + $this->_xh['vt'] = 'null'; + $this->_xh['value'] = null; + $this->_xh['lv'] = 3; break; - } + } + + // drop through intentionally if nil extension not enabled + case 'PARAMS': + case 'FAULT': + case 'METHODCALL': + case 'METHORESPONSE': + break; + + default: + // End of INVALID ELEMENT + // Should we add an assert here for unreachable code? When an invalid element is found in xmlrpc_se, + // + break; } } @@ -598,12 +677,13 @@ class XMLParser public function xmlrpc_cd($parser, $data) { // skip processing if xml fault already detected - if ($this->_xh['isf'] < 2) { - // "lookforvalue==3" means that we've found an entire value - // and should discard any further character data - if ($this->_xh['lv'] != 3) { - $this->_xh['ac'] .= $data; - } + if ($this->_xh['isf'] >= 2) { + return; + } + + // "lookforvalue == 3" means that we've found an entire value and should discard any further character data + if ($this->_xh['lv'] != 3) { + $this->_xh['ac'] .= $data; } } @@ -619,13 +699,13 @@ class XMLParser public function xmlrpc_dh($parser, $data) { // skip processing if xml fault already detected - if ($this->_xh['isf'] < 2) { - if (substr($data, 0, 1) == '&' && substr($data, -1, 1) == ';') { - $this->_xh['ac'] .= $data; - } + if ($this->_xh['isf'] >= 2) { + return; } - //return true; + if (substr($data, 0, 1) == '&' && substr($data, -1, 1) == ';') { + $this->_xh['ac'] .= $data; + } } /** diff --git a/src/Server.php b/src/Server.php index badb36eb..20bf0a93 100644 --- a/src/Server.php +++ b/src/Server.php @@ -2,6 +2,7 @@ namespace PhpXmlRpc; +use PhpXmlRpc\Exception\PhpXmlrpcException; use PhpXmlRpc\Helper\Charset; use PhpXmlRpc\Helper\Logger; use PhpXmlRpc\Helper\XMLParser; @@ -17,8 +18,8 @@ class Server /** * @var string - * Defines how functions in $dmap will be invoked: either using an xmlrpc request object or plain php values. - * Valid strings are 'xmlrpcvals', 'phpvals' or 'epivals'. + * Defines how functions in $dmap will be invoked: either using an xml-rpc Request object or plain php values. + * Valid strings are 'xmlrpcvals', 'phpvals' or 'epivals' (only for use by polyfill-xmlrpc). * * @todo create class constants for these */ @@ -27,7 +28,7 @@ class Server /** * @var array * 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'. + * when the functions_parameters_type member is set to 'phpvals'. * @see Encoder::encode for a list of values */ public $phpvals_encoding_options = array('auto_dates'); @@ -35,13 +36,17 @@ class Server /** * @var int * Controls whether the server is going to echo debugging messages back to the client as comments in response body. - * Valid values: 0,1,2,3 + * Valid values: + * 0 = + * 1 = + * 2 = + * 3 = */ public $debug = 1; /** * @var int - * Controls behaviour of server when the invoked user function throws an exception: + * Controls behaviour of server when the invoked method-handler function throws an exception (within the `execute` method): * 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 @@ -169,7 +174,7 @@ class Server * - docstring (optional) * - signature (array, optional) * - signature_docs (array, optional) - * - parameters_type (string, optional) - currently broken + * - parameters_type (string, optional) * @param boolean $serviceNow set to false to prevent the server from running upon construction */ public function __construct($dispatchMap = null, $serviceNow = true) @@ -303,15 +308,17 @@ class Server $this->debugmsg("+++GOT+++\n" . $data . "\n+++END+++"); } - $r = $this->parseRequestHeaders($data, $reqCharset, $respCharset, $respEncoding); - if (!$r) { + $resp = $this->parseRequestHeaders($data, $reqCharset, $respCharset, $respEncoding); + if (!$resp) { // this actually executes the request - $r = $this->parseRequest($data, $reqCharset); + $resp = $this->parseRequest($data, $reqCharset); - // save full body of request into response, for more debugging usages. - // Note that this is the _request_ data, not the response's own data, unlike what happens client-side - /// @todo try to move this injection to the resp. constructor or use a non-deprecated access method - $r->raw_data = $rawData; + // save full body of request into response, for debugging purposes. + // NB: this is the _request_ data, not the response's own data, unlike what happens client-side + /// @todo try to move this injection to the resp. constructor or use a non-deprecated access method. Or, even + /// better: just avoid setting this, and set debug info of the received http request in the request + /// object instead? It's not like the developer misses access to _SERVER, _COOKIES though... + $resp->raw_data = $rawData; } if ($this->debug > 2 && static::$_xmlrpcs_occurred_errors != '') { @@ -324,11 +331,11 @@ class Server $payload = $payload . $this->serializeDebug($respCharset); } - // Do not create response serialization if it has already happened. Helps building json magic - if (empty($r->payload)) { - $r->serialize($respCharset); + // Do not create response serialization if it has already happened. Helps to build json magic + if (empty($resp->payload)) { + $resp->serialize($respCharset); } - $payload = $payload . $r->payload; + $payload = $payload . $resp->payload; if ($returnPayload) { return $payload; @@ -337,7 +344,7 @@ class Server // 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... if (!headers_sent()) { - header('Content-Type: ' . $r->content_type); + header('Content-Type: ' . $resp->content_type); // we do not know if client actually told us an accepted charset, but if it did we have to tell it what we did header("Vary: Accept-Charset"); @@ -368,8 +375,8 @@ class Server print $payload; - // return request, in case subclasses want it - return $r; + // return response, in case subclasses want it + return $resp; } /** @@ -383,12 +390,12 @@ class Server * @param string $doc method documentation * @param array[] $sigDoc the array of valid method signatures docs, following the format of $sig but with * descriptions instead of types (one string for return type, one per param) + * @param string $parametersType to allow single method handlers to receive php values instead of a Request, or vice-versa * @return void * * @todo raise a warning if the user tries to register a 'system.' method - * @todo allow setting parameters_type */ - public function add_to_map($methodName, $function, $sig = null, $doc = false, $sigDoc = false) + public function add_to_map($methodName, $function, $sig = null, $doc = false, $sigDoc = false, $parametersType = false) { $this->dmap[$methodName] = array( 'function' => $function, @@ -400,6 +407,9 @@ class Server if ($sigDoc) { $this->dmap[$methodName]['signature_docs'] = $sigDoc; } + if ($parametersType) { + $this->dmap[$methodName]['parameters_type'] = $parametersType; + } } /** @@ -558,8 +568,8 @@ class Server } /** - * Parse an xml chunk containing an xmlrpc request and execute the corresponding - * php function registered with the server. + * Parse an xml chunk containing an xml-rpc request and execute the corresponding php function registered with the + * server. * @internal this function will become protected in the future * * @param string $data the xml request @@ -605,27 +615,33 @@ class Server } else { $options = array(XML_OPTION_TARGET_ENCODING => PhpXmlRpc::$xmlrpc_internalencoding); } + // register a callback with the xml parser for when it finds the method name + $options['methodname_callback'] = array($this, 'methodNameCallback'); $xmlRpcParser = $this->getParser(); - $xmlRpcParser->parse($data, $this->functions_parameters_type, XMLParser::ACCEPT_REQUEST, $options); + try { + $xmlRpcParser->parse($data, $this->functions_parameters_type, XMLParser::ACCEPT_REQUEST, $options); + } catch (PhpXmlrpcException $e) { + return new Response(0, $e->getCode(), $e->getMessage()); + } + +/// @todo handle the (unlikely) case of _xh['isf'] = 4 if ($xmlRpcParser->_xh['isf'] > 2) { // (BC) we return XML error as a faultCode preg_match('/^XML error ([0-9]+)/', $xmlRpcParser->_xh['isf_reason'], $matches); - $r = new Response( + return new Response( 0, PhpXmlRpc::$xmlrpcerrxml + (int)$matches[1], $xmlRpcParser->_xh['isf_reason']); } elseif ($xmlRpcParser->_xh['isf']) { - $r = new Response( + return new Response( 0, PhpXmlRpc::$xmlrpcerr['invalid_request'], PhpXmlRpc::$xmlrpcstr['invalid_request'] . ' ' . $xmlRpcParser->_xh['isf_reason']); } else { - // small layering violation in favor of speed and memory usage: - // we should allow the 'execute' method handle this, but in the - // most common scenario (xmlrpc values type server with some methods - // registered as phpvals) that would mean a useless encode+decode pass - /// @bug when parameters_type is set in the method, we still get full-fledged Value objects + // small layering violation in favor of speed and memory usage: we should allow the 'execute' method handle + // this, but in the most common scenario (xmlrpc values 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[$xmlRpcParser->_xh['method']]['parameters_type']) && ($this->dmap[$xmlRpcParser->_xh['method']]['parameters_type'] != 'xmlrpcvals') @@ -634,7 +650,8 @@ class Server if ($this->debug > 1) { $this->debugmsg("\n+++PARSED+++\n" . var_export($xmlRpcParser->_xh['params'], true) . "\n+++END+++"); } - $r = $this->execute($xmlRpcParser->_xh['method'], $xmlRpcParser->_xh['params'], $xmlRpcParser->_xh['pt']); + + return $this->execute($xmlRpcParser->_xh['method'], $xmlRpcParser->_xh['params'], $xmlRpcParser->_xh['pt']); } else { // build a Request object with data parsed from xml $req = new Request($xmlRpcParser->_xh['method']); @@ -646,19 +663,18 @@ class Server if ($this->debug > 1) { $this->debugmsg("\n+++PARSED+++\n" . var_export($req, true) . "\n+++END+++"); } - $r = $this->execute($req); + + return $this->execute($req); } } - - return $r; } /** * Execute a method invoked by the client, checking parameters used. * * @param Request|string $req either a Request obj or a method name - * @param mixed[] $params array with method parameters as php types (only if m is method name) - * @param string[] $paramTypes array with xmlrpc types of method parameters (only if m is method name) + * @param mixed[] $params array with method parameters as php types (only if $req is method name) + * @param string[] $paramTypes array with xmlrpc types of method parameters (only if $req is method name) * @return Response * * @throws \Exception in case the executed method does throw an exception (and depending on server configuration) @@ -669,14 +685,15 @@ class Server static::$_xmlrpc_debuginfo = ''; if (is_object($req)) { - $methName = $req->method(); + $methodName = $req->method(); } else { - $methName = $req; + $methodName = $req; } - $sysCall = $this->isSyscall($methName); + + $sysCall = $this->isSyscall($methodName); $dmap = $sysCall ? $this->getSystemDispatchMap() : $this->dmap; - if (!isset($dmap[$methName]['function'])) { + if (!isset($dmap[$methodName]['function'])) { // No such method return new Response(0, PhpXmlRpc::$xmlrpcerr['unknown_method'], @@ -684,8 +701,8 @@ class Server } // Check signature - if (isset($dmap[$methName]['signature'])) { - $sig = $dmap[$methName]['signature']; + if (isset($dmap[$methodName]['signature'])) { + $sig = $dmap[$methodName]['signature']; if (is_object($req)) { list($ok, $errStr) = $this->verifySignature($req, $sig); } else { @@ -701,12 +718,14 @@ class Server } } - $func = $dmap[$methName]['function']; + $func = $dmap[$methodName]['function']; + // let the 'class::function' syntax be accepted in dispatch maps if (is_string($func) && strpos($func, '::')) { $func = explode('::', $func); } + // build string representation of function 'name' if (is_array($func)) { if (is_object($func[0])) { $funcName = get_class($func[0]) . '->' . $func[1]; @@ -729,8 +748,8 @@ class Server ); } - // If debug level is 3, we should catch all errors generated during - // processing of user function, and log them as part of response + // If debug level is 3, we should catch all errors generated during processing of user function, and log them + // as part of response if ($this->debug > 2) { self::$_xmlrpcs_prev_ehandler = set_error_handler(array('\PhpXmlRpc\Server', '_xmlrpcs_errorHandler')); } @@ -738,6 +757,7 @@ class Server try { // Allow mixed-convention servers if (is_object($req)) { + // call an 'xml-rpc aware' function if ($sysCall) { $r = call_user_func($func, $this, $req); } else { @@ -763,14 +783,13 @@ class Server } else { // 3rd API convention for method-handling functions: EPI-style if ($this->functions_parameters_type == 'epivals') { - $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 error response + $r = call_user_func_array($func, array($methodName, $params, $this->user_data)); + // mimic EPI behaviour: if we get an array that looks like an error, make it an error response if (is_array($r) && array_key_exists('faultCode', $r) && array_key_exists('faultString', $r)) { $r = new Response(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 + // functions using EPI api should NOT return resp objects, so make sure we encode the + // return type correctly $encoder = new Encoder(); $r = new Response($encoder->encode($r, array('extension_api'))); } @@ -780,7 +799,8 @@ class Server } // the return type can be either a Response object or a plain php value... if (!is_a($r, '\PhpXmlRpc\Response')) { - // what should we assume here about automatic encoding of datetimes and php classes instances??? + // q: what should we assume here about automatic encoding of datetimes and php classes instances? + // a: let the user decide $encoder = new Encoder(); $r = new Response($encoder->encode($r, $this->phpvals_encoding_options)); } @@ -832,9 +852,10 @@ class Server $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_error'], PhpXmlRpc::$xmlrpcstr['server_error']); } } + if ($this->debug > 2) { - // note: restore the error handler we found before calling the - // user func, even if it has been changed inside the func itself + // note: restore the error handler we found before calling the user func, even if it has been changed + // inside the func itself if (self::$_xmlrpcs_prev_ehandler) { set_error_handler(self::$_xmlrpcs_prev_ehandler); } else { @@ -845,6 +866,51 @@ class Server return $r; } + /** + * Registered as callback for when the XMLParser has found the name of the method to execute. + * Handling that early allows to 1. stop parsing the rest of the xml if there is no such method registered, and + * 2. tweak the type of data that the parser will return, in case the server uses mixed-calling-convention + * + * @internal + * @param $methodName + * @param XMLParser $xmlParser + * @param resource $parser + * @return void + * @throws PhpXmlrpcException + * + * @todo feature creep - we could validate here that the method in the dispatch map is valid, but that would mean + * dirtying a lot the logic, as we would have back to both parseRequest() and execute() methods the info + * about the matched method handler, in order to avoid doing the work twice... + */ + public function methodNameCallback($methodName, $xmlParser, $parser) + { + $sysCall = $this->isSyscall($methodName); + $dmap = $sysCall ? $this->getSystemDispatchMap() : $this->dmap; + + if (!isset($dmap[$methodName]['function'])) { + // No such method + throw new PhpXmlrpcException(0, PhpXmlRpc::$xmlrpcstr['unknown_method'], PhpXmlRpc::$xmlrpcerr['unknown_method']); + } + + // alter on-the-fly the config of the xml parser if needed + if (isset($dmap[$methodName]['parameters_type']) && + $dmap[$methodName]['parameters_type'] != $this->functions_parameters_type) { + /// @todo this should be done by a method of the XMLParser + switch ($dmap[$methodName]['parameters_type']) { + case XMLParser::RETURN_PHP: + xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast'); + break; + case XMLParser::RETURN_EPIVALS: + xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_epi'); + break; + /// @todo log a warning on unsupported return type + case XMLParser::RETURN_XMLRPCVALS: + default: + xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee'); + } + } + } + /** * Add a string to the 'internal debug message' (separate from 'user debug message'). * @@ -878,6 +944,7 @@ class Server return (strpos($methName, "system.") === 0); } + /** * @return array[] */