*
* @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
{
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(),
'rt' => '',
);
+ /**
+ * @var array[]
+ * @internal
+ */
public $xmlrpc_valid_parents = array(
'VALUE' => array('MEMBER', 'DATA', 'PARAM', 'FAULT'),
'BOOLEAN' => array('VALUE'),
'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()
{
}
/**
- * @param array $options passed to the xml parser
+ * @param int[] $options passed to the xml parser
*/
public function __construct(array $options = array())
{
/**
* @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())
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('');
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');
}
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;
}
/**
* @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;
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 <NIL/> 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;
}
}
*/
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 <VALUE></VALUE>
- 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 <VALUE></VALUE>
+ 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-.<space> 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-<space> 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-.<space> 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-<space> 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;
}
}
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;
}
}
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;
+ }
}
/**
namespace PhpXmlRpc;
+use PhpXmlRpc\Exception\PhpXmlrpcException;
use PhpXmlRpc\Helper\Charset;
use PhpXmlRpc\Helper\Logger;
use PhpXmlRpc\Helper\XMLParser;
/**
* @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
*/
/**
* @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');
/**
* @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
* - 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)
$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 != '') {
$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;
// 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");
print $payload;
- // return request, in case subclasses want it
- return $r;
+ // return response, in case subclasses want it
+ return $resp;
}
/**
* @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,
if ($sigDoc) {
$this->dmap[$methodName]['signature_docs'] = $sigDoc;
}
+ if ($parametersType) {
+ $this->dmap[$methodName]['parameters_type'] = $parametersType;
+ }
}
/**
}
/**
- * 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
} 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')
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']);
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)
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'],
}
// 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 {
}
}
- $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];
);
}
- // 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'));
}
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 {
} 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')));
}
}
// 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));
}
$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 {
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').
*
return (strpos($methName, "system.") === 0);
}
+
/**
* @return array[]
*/