From: avl Date: Mon, 29 Sep 2008 12:14:21 +0000 (+0300) Subject: xmlrpc X-Git-Tag: 0.5~480 X-Git-Url: https://git.andy128k.dev/?a=commitdiff_plain;h=18a247ce0aa5c700246d9fa0bba568d0875759c7;p=ipf.git xmlrpc --- diff --git a/ipf/xmlrpc/fault.php b/ipf/xmlrpc/fault.php new file mode 100644 index 0000000..7b1d7b7 --- /dev/null +++ b/ipf/xmlrpc/fault.php @@ -0,0 +1,183 @@ + 'Unknown Error', + + // 610 - 619 reflection errors + 610 => 'Invalid method class', + 611 => 'Unable to attach function or callback; not callable', + 612 => 'Unable to load array; not an array', + 613 => 'One or more method records are corrupt or otherwise unusable', + + // 620 - 629 dispatch errors + 620 => 'Method does not exist', + 621 => 'Error instantiating class to invoke method', + 622 => 'Method missing implementation', + 623 => 'Calling parameters do not match signature', + + // 630 - 639 request errors + 630 => 'Unable to read request', + 631 => 'Failed to parse request', + 632 => 'Invalid request, no method passed; request must contain a \'methodName\' tag', + 633 => 'Param must contain a value', + 634 => 'Invalid method name', + 635 => 'Invalid XML provided to request', + 636 => 'Error creating xmlrpc value', + + // 640 - 649 system.* errors + 640 => 'Method does not exist', + + // 650 - 659 response errors + 650 => 'Invalid XML provided for response', + 651 => 'Failed to parse response', + 652 => 'Invalid response', + 653 => 'Invalid XMLRPC value in response', + ); + + public function __construct($code = 404, $message = '') + { + $this->setCode($code); + $code = $this->getCode(); + + if (empty($message) && isset($this->_internal[$code])) { + $message = $this->_internal[$code]; + } elseif (empty($message)) { + $message = 'Unknown error'; + } + $this->setMessage($message); + } + + public function setCode($code) + { + $this->_code = (int) $code; + return $this; + } + + public function getCode() + { + return $this->_code; + } + + public function setMessage($message) + { + $this->_message = (string) $message; + return $this; + } + + public function getMessage() + { + return $this->_message; + } + + public function setEncoding($encoding) + { + $this->_encoding = $encoding; + return $this; + } + + public function getEncoding() + { + return $this->_encoding; + } + + public function loadXml($fault) + { + if (!is_string($fault)) { + throw new IPF_Exception('Invalid XML provided to fault'); + } + + try { + $xml = @new SimpleXMLElement($fault); + } catch (Exception $e) { + // Not valid XML + throw new IPF_Exception('Failed to parse XML fault: ' . $e->getMessage(), 500); + } + + // Check for fault + if (!$xml->fault) { + // Not a fault + return false; + } + + if (!$xml->fault->value->struct) { + // not a proper fault + throw new IPF_Exception('Invalid fault structure', 500); + } + + $structXml = $xml->fault->value->asXML(); + $structXml = preg_replace('/<\?xml version=.*?\?>/i', '', $structXml); + $struct = IPF_XmlRpc_Value::getXmlRpcValue(trim($structXml), IPF_XmlRpc_Value::XML_STRING); + $struct = $struct->getValue(); + + if (isset($struct['faultCode'])) { + $code = $struct['faultCode']; + } + if (isset($struct['faultString'])) { + $message = $struct['faultString']; + } + + if (empty($code) && empty($message)) { + throw new IPF_Exception('Fault code and string required'); + } + + if (empty($code)) { + $code = '404'; + } + + if (empty($message)) { + if (isset($this->_internal[$code])) { + $message = $this->_internal[$code]; + } else { + $message = 'Unknown Error'; + } + } + + $this->setCode($code); + $this->setMessage($message); + + return true; + } + + public static function isFault($xml) + { + $fault = new self(); + try { + $isFault = $fault->loadXml($xml); + } catch (IPF_Exception $e) { + $isFault = false; + } + + return $isFault; + } + + public function saveXML() + { + // Create fault value + $faultStruct = array( + 'faultCode' => $this->getCode(), + 'faultString' => $this->getMessage() + ); + $value = IPF_XmlRpc_Value::getXmlRpcValue($faultStruct); + $valueDOM = new DOMDocument('1.0', $this->getEncoding()); + $valueDOM->loadXML($value->saveXML()); + + // Build response XML + $dom = new DOMDocument('1.0', 'ISO-8859-1'); + $r = $dom->appendChild($dom->createElement('methodResponse')); + $f = $r->appendChild($dom->createElement('fault')); + $f->appendChild($dom->importNode($valueDOM->documentElement, 1)); + + return $dom->saveXML(); + } + + public function __toString() + { + return $this->saveXML(); + } +} diff --git a/ipf/xmlrpc/request.php b/ipf/xmlrpc/request.php new file mode 100644 index 0000000..39c4edc --- /dev/null +++ b/ipf/xmlrpc/request.php @@ -0,0 +1,257 @@ +setMethod($method); + } + + if ($params !== null) { + $this->setParams($params); + } + } + + public function setEncoding($encoding) + { + $this->_encoding = $encoding; + return $this; + } + + public function getEncoding() + { + return $this->_encoding; + } + + public function setMethod($method) + { + if (!is_string($method) || !preg_match('/^[a-z0-9_.:\/]+$/i', $method)) { + $this->_fault = new IPF_XmlRpc_Fault(634, 'Invalid method name ("' . $method . '")'); + $this->_fault->setEncoding($this->getEncoding()); + return false; + } + + $this->_method = $method; + return true; + } + + public function getMethod() + { + return $this->_method; + } + + public function addParam($value, $type = null) + { + $this->_params[] = $value; + if (null === $type) { + // Detect type if not provided explicitly + if ($value instanceof IPF_XmlRpc_Value) { + $type = $value->getType(); + } else { + $xmlRpcValue = IPF_XmlRpc_Value::getXmlRpcValue($value); + $type = $xmlRpcValue->getType(); + } + } + $this->_types[] = $type; + $this->_xmlRpcParams[] = array('value' => $value, 'type' => $type); + } + + public function setParams() + { + $argc = func_num_args(); + $argv = func_get_args(); + if (0 == $argc) { + return; + } + + if ((1 == $argc) && is_array($argv[0])) { + $params = array(); + $types = array(); + $wellFormed = true; + foreach ($argv[0] as $arg) { + if (!is_array($arg) || !isset($arg['value'])) { + $wellFormed = false; + break; + } + $params[] = $arg['value']; + + if (!isset($arg['type'])) { + $xmlRpcValue = IPF_XmlRpc_Value::getXmlRpcValue($arg['value']); + $arg['type'] = $xmlRpcValue->getType(); + } + $types[] = $arg['type']; + } + if ($wellFormed) { + $this->_xmlRpcParams = $argv[0]; + $this->_params = $params; + $this->_types = $types; + } else { + $this->_params = $argv[0]; + $this->_types = array(); + $xmlRpcParams = array(); + foreach ($argv[0] as $arg) { + if ($arg instanceof IPF_XmlRpc_Value) { + $type = $arg->getType(); + } else { + $xmlRpcValue = IPF_XmlRpc_Value::getXmlRpcValue($arg); + $type = $xmlRpcValue->getType(); + } + $xmlRpcParams[] = array('value' => $arg, 'type' => $type); + $this->_types[] = $type; + } + $this->_xmlRpcParams = $xmlRpcParams; + } + return; + } + + $this->_params = $argv; + $this->_types = array(); + $xmlRpcParams = array(); + foreach ($argv as $arg) { + if ($arg instanceof IPF_XmlRpc_Value) { + $type = $arg->getType(); + } else { + $xmlRpcValue = IPF_XmlRpc_Value::getXmlRpcValue($arg); + $type = $xmlRpcValue->getType(); + } + $xmlRpcParams[] = array('value' => $arg, 'type' => $type); + $this->_types[] = $type; + } + $this->_xmlRpcParams = $xmlRpcParams; + } + + public function getParams() + { + return $this->_params; + } + + public function getTypes() + { + return $this->_types; + } + + public function loadXml($request) + { + if (!is_string($request)) { + $this->_fault = new IPF_XmlRpc_Fault(635); + $this->_fault->setEncoding($this->getEncoding()); + return false; + } + + try { + $xml = @new SimpleXMLElement($request); + } catch (Exception $e) { + // Not valid XML + $this->_fault = new IPF_XmlRpc_Fault(631); + $this->_fault->setEncoding($this->getEncoding()); + return false; + } + + // Check for method name + if (empty($xml->methodName)) { + // Missing method name + $this->_fault = new IPF_XmlRpc_Fault(632); + $this->_fault->setEncoding($this->getEncoding()); + return false; + } + + $this->_method = (string) $xml->methodName; + + // Check for parameters + if (!empty($xml->params)) { + $types = array(); + $argv = array(); + foreach ($xml->params->children() as $param) { + if (! $param->value instanceof SimpleXMLElement) { + $this->_fault = new IPF_XmlRpc_Fault(633); + $this->_fault->setEncoding($this->getEncoding()); + return false; + } + + try { + $param = IPF_XmlRpc_Value::getXmlRpcValue($param->value, IPF_XmlRpc_Value::XML_STRING); + $types[] = $param->getType(); + $argv[] = $param->getValue(); + } catch (Exception $e) { + $this->_fault = new IPF_XmlRpc_Fault(636); + $this->_fault->setEncoding($this->getEncoding()); + return false; + } + } + + $this->_types = $types; + $this->_params = $argv; + } + + $this->_xml = $request; + + return true; + } + + public function isFault() + { + return $this->_fault instanceof IPF_XmlRpc_Fault; + } + + public function getFault() + { + return $this->_fault; + } + + protected function _getXmlRpcParams() + { + $params = array(); + if (is_array($this->_xmlRpcParams)) { + foreach ($this->_xmlRpcParams as $param) { + $value = $param['value']; + $type = isset($param['type']) ? $param['type'] : IPF_XmlRpc_Value::AUTO_DETECT_TYPE; + + if (!$value instanceof IPF_XmlRpc_Value) { + $value = IPF_XmlRpc_Value::getXmlRpcValue($value, $type); + } + $params[] = $value; + } + } + + return $params; + } + + public function saveXML() + { + $args = $this->_getXmlRpcParams(); + $method = $this->getMethod(); + + $dom = new DOMDocument('1.0', $this->getEncoding()); + $mCall = $dom->appendChild($dom->createElement('methodCall')); + $mName = $mCall->appendChild($dom->createElement('methodName', $method)); + + if (is_array($args) && count($args)) { + $params = $mCall->appendChild($dom->createElement('params')); + + foreach ($args as $arg) { + /* @var $arg IPF_XmlRpc_Value */ + $argDOM = new DOMDocument('1.0', $this->getEncoding()); + $argDOM->loadXML($arg->saveXML()); + + $param = $params->appendChild($dom->createElement('param')); + $param->appendChild($dom->importNode($argDOM->documentElement, 1)); + } + } + + return $dom->saveXML(); + } + + public function __toString() + { + return $this->saveXML(); + } +} diff --git a/ipf/xmlrpc/request/http.php b/ipf/xmlrpc/request/http.php new file mode 100644 index 0000000..f66114e --- /dev/null +++ b/ipf/xmlrpc/request/http.php @@ -0,0 +1,57 @@ +_fault = new IPF_Exception(630); + return; + } + + $xml = ''; + while (!feof($fh)) { + $xml .= fgets($fh); + } + fclose($fh); + + $this->_xml = $xml; + + $this->loadXml($xml); + } + + public function getRawRequest() + { + return $this->_xml; + } + + public function getHeaders() + { + if (null === $this->_headers) { + $this->_headers = array(); + foreach ($_SERVER as $key => $value) { + if ('HTTP_' == substr($key, 0, 5)) { + $header = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5))))); + $this->_headers[$header] = $value; + } + } + } + + return $this->_headers; + } + + public function getFullRequest() + { + $request = ''; + foreach ($this->getHeaders() as $key => $value) { + $request .= $key . ': ' . $value . "\n"; + } + + $request .= $this->_xml; + + return $request; + } +} diff --git a/ipf/xmlrpc/request/stdin.php b/ipf/xmlrpc/request/stdin.php new file mode 100644 index 0000000..0538d2b --- /dev/null +++ b/ipf/xmlrpc/request/stdin.php @@ -0,0 +1,30 @@ +_fault = new IPF_XmlRpc_Fault(630); + return; + } + + $xml = ''; + while (!feof($fh)) { + $xml .= fgets($fh); + } + fclose($fh); + + $this->_xml = $xml; + + $this->loadXml($xml); + } + + public function getRawRequest() + { + return $this->_xml; + } +} diff --git a/ipf/xmlrpc/response.php b/ipf/xmlrpc/response.php new file mode 100644 index 0000000..5f2f78a --- /dev/null +++ b/ipf/xmlrpc/response.php @@ -0,0 +1,121 @@ +setReturnValue($return, $type); + } + + public function setEncoding($encoding) + { + $this->_encoding = $encoding; + return $this; + } + + public function getEncoding() + { + return $this->_encoding; + } + + public function setReturnValue($value, $type = null) + { + $this->_return = $value; + $this->_type = (string) $type; + } + + public function getReturnValue() + { + return $this->_return; + } + + protected function _getXmlRpcReturn() + { + return IPF_XmlRpc_Value::getXmlRpcValue($this->_return); + } + + public function isFault() + { + return $this->_fault instanceof IPF_XmlRpc_Fault; + } + + public function getFault() + { + return $this->_fault; + } + + public function loadXml($response) + { + if (!is_string($response)) { + $this->_fault = new IPF_XmlRpc_Fault(650); + $this->_fault->setEncoding($this->getEncoding()); + return false; + } + + try { + $xml = @new SimpleXMLElement($response); + } catch (Exception $e) { + // Not valid XML + $this->_fault = new IPF_XmlRpc_Fault(651); + $this->_fault->setEncoding($this->getEncoding()); + return false; + } + + if (!empty($xml->fault)) { + // fault response + $this->_fault = new IPF_XmlRpc_Fault(); + $this->_fault->setEncoding($this->getEncoding()); + $this->_fault->loadXml($response); + return false; + } + + if (empty($xml->params)) { + // Invalid response + $this->_fault = new IPF_XmlRpc_Fault(652); + $this->_fault->setEncoding($this->getEncoding()); + return false; + } + + try { + if (!isset($xml->params) || !isset($xml->params->param) || !isset($xml->params->param->value)) { + throw new IPF_Exception('Missing XML-RPC value in XML'); + } + $valueXml = $xml->params->param->value->asXML(); + $valueXml = preg_replace('/<\?xml version=.*?\?>/i', '', $valueXml); + $value = IPF_XmlRpc_Value::getXmlRpcValue(trim($valueXml), IPF_XmlRpc_Value::XML_STRING); + } catch (IPF_Exception $e) { + $this->_fault = new IPF_XmlRpc_Fault(653); + $this->_fault->setEncoding($this->getEncoding()); + return false; + } + + $this->setReturnValue($value->getValue()); + return true; + } + + public function saveXML() + { + $value = $this->_getXmlRpcReturn(); + $valueDOM = new DOMDocument('1.0', $this->getEncoding()); + $valueDOM->loadXML($value->saveXML()); + + $dom = new DOMDocument('1.0', $this->getEncoding()); + $response = $dom->appendChild($dom->createElement('methodResponse')); + $params = $response->appendChild($dom->createElement('params')); + $param = $params->appendChild($dom->createElement('param')); + + $param->appendChild($dom->importNode($valueDOM->documentElement, true)); + + return $dom->saveXML(); + } + + public function __toString() + { + return $this->saveXML(); + } +} diff --git a/ipf/xmlrpc/response/http.php b/ipf/xmlrpc/response/http.php new file mode 100644 index 0000000..fa767b5 --- /dev/null +++ b/ipf/xmlrpc/response/http.php @@ -0,0 +1,12 @@ +getEncoding())); + } + return parent::__toString(); + } +} diff --git a/ipf/xmlrpc/server.php b/ipf/xmlrpc/server.php new file mode 100644 index 0000000..a3a5a3a --- /dev/null +++ b/ipf/xmlrpc/server.php @@ -0,0 +1,428 @@ + 'i4', + 'int' => 'int', + 'integer' => 'int', + 'double' => 'double', + 'float' => 'double', + 'real' => 'double', + 'boolean' => 'boolean', + 'bool' => 'boolean', + 'true' => 'boolean', + 'false' => 'boolean', + 'string' => 'string', + 'str' => 'string', + 'base64' => 'base64', + 'dateTime.iso8601' => 'dateTime.iso8601', + 'date' => 'dateTime.iso8601', + 'time' => 'dateTime.iso8601', + 'time' => 'dateTime.iso8601', + 'array' => 'array', + 'struct' => 'struct', + 'null' => 'nil', + 'nil' => 'nil', + 'void' => 'void', + 'mixed' => 'struct' + ); + + public function __construct() + { + // Setup system.* methods + $system = array( + 'listMethods', + 'methodHelp', + 'methodSignature', + 'multicall' + ); + + $class = IPF_Server_Reflection::reflectClass($this); + foreach ($system as $method) { + $reflection = new IPF_Server_Reflection_Method($class, new ReflectionMethod($this, $method), 'system'); + $reflection->system = true; + $this->_methods[] = $reflection; + } + + $this->_buildDispatchTable(); + } + + protected function _fixTypes(IPF_Server_Reflection_Function_Abstract $method) + { + foreach ($method->getPrototypes() as $prototype) { + foreach ($prototype->getParameters() as $param) { + $pType = $param->getType(); + if (isset($this->_typeMap[$pType])) { + $param->setType($this->_typeMap[$pType]); + } else { + $param->setType('void'); + } + } + } + } + + protected function _buildDispatchTable() + { + $table = array(); + foreach ($this->_methods as $dispatchable) { + if ($dispatchable instanceof IPF_Server_Reflection_Function_Abstract) { + // function/method call + $ns = $dispatchable->getNamespace(); + $name = $dispatchable->getName(); + $name = empty($ns) ? $name : $ns . '.' . $name; + + if (isset($table[$name])) { + throw new IPF_Exception('Duplicate method registered: ' . $name); + } + $table[$name] = $dispatchable; + $this->_fixTypes($dispatchable); + + continue; + } + + if ($dispatchable instanceof IPF_Server_Reflection_Class) { + foreach ($dispatchable->getMethods() as $method) { + $ns = $method->getNamespace(); + $name = $method->getName(); + $name = empty($ns) ? $name : $ns . '.' . $name; + + if (isset($table[$name])) { + throw new IPF_Exception('Duplicate method registered: ' . $name); + } + $table[$name] = $method; + $this->_fixTypes($method); + continue; + } + } + } + + $this->_table = $table; + } + + public function setEncoding($encoding) + { + $this->_encoding = $encoding; + return $this; + } + + public function getEncoding() + { + return $this->_encoding; + } + + public function addFunction($function, $namespace = '') + { + if (!is_string($function) && !is_array($function)) { + throw new IPF_Exception('Unable to attach function; invalid', 611); + } + + $argv = null; + if (2 < func_num_args()) { + $argv = func_get_args(); + $argv = array_slice($argv, 2); + } + + $function = (array) $function; + foreach ($function as $func) { + if (!is_string($func) || !function_exists($func)) { + throw new IPF_Exception('Unable to attach function; invalid', 611); + } + $this->_methods[] = IPF_Server_Reflection::reflectFunction($func, $argv, $namespace); + } + + $this->_buildDispatchTable(); + } + + public function loadFunctions($array) + { + if (!is_array($array)) { + throw new IPF_Exception('Unable to load array; not an array', 612); + } + + foreach ($array as $key => $value) { + if (!$value instanceof IPF_Server_Reflection_Function_Abstract + && !$value instanceof IPF_Server_Reflection_Class) + { + throw new IPF_Exception('One or more method records are corrupt or otherwise unusable', 613); + } + + if ($value->system) { + unset($array[$key]); + } + } + + foreach ($array as $dispatchable) { + $this->_methods[] = $dispatchable; + } + + $this->_buildDispatchTable(); + } + + public function setPersistence($class = null) + { + } + + public function setClass($class, $namespace = '', $argv = null) + { + if (is_string($class) && !class_exists($class)) { + if (!class_exists($class)) { + throw new IPF_Exception('Invalid method class', 610); + } + } + + $argv = null; + if (3 < func_num_args()) { + $argv = func_get_args(); + $argv = array_slice($argv, 3); + } + + $this->_methods[] = IPF_Reflection::reflectClass($class, $argv, $namespace); + $this->_buildDispatchTable(); + } + + public function setRequest($request) + { + if (is_string($request) && class_exists($request)) { + $request = new $request(); + if (!$request instanceof IPF_XmlRpc_Request) { + throw new IPF_Exception('Invalid request class'); + } + $request->setEncoding($this->getEncoding()); + } elseif (!$request instanceof IPF_XmlRpc_Request) { + throw new IPF_Exception('Invalid request object'); + } + + $this->_request = $request; + return $this; + } + + public function getRequest() + { + return $this->_request; + } + + public function fault($fault, $code = 404) + { + if (!$fault instanceof Exception) { + $fault = (string) $fault; + $fault = new IPF_Exception($fault, $code); + } + return IPF_XmlRpc_Server_Fault::getInstance($fault); + } + + protected function _handle(IPF_XmlRpc_Request $request) + { + $method = $request->getMethod(); + + // Check for valid method + if (!isset($this->_table[$method])) { + throw new IPF_Exception('Method "' . $method . '" does not exist', 620); + } + + $info = $this->_table[$method]; + $params = $request->getParams(); + $argv = $info->getInvokeArguments(); + if (0 < count($argv)) { + $params = array_merge($params, $argv); + } + + // Check calling parameters against signatures + $matched = false; + $sigCalled = $request->getTypes(); + + $sigLength = count($sigCalled); + $paramsLen = count($params); + if ($sigLength < $paramsLen) { + for ($i = $sigLength; $i < $paramsLen; ++$i) { + $xmlRpcValue = IPF_XmlRpc_Value::getXmlRpcValue($params[$i]); + $sigCalled[] = $xmlRpcValue->getType(); + } + } + + $signatures = $info->getPrototypes(); + foreach ($signatures as $signature) { + $sigParams = $signature->getParameters(); + $tmpParams = array(); + foreach ($sigParams as $param) { + $tmpParams[] = $param->getType(); + } + if ($sigCalled === $tmpParams) { + $matched = true; + break; + } + } + if (!$matched) { + throw new IPF_Exception('Calling parameters do not match signature', 623); + } + + if ($info instanceof IPF_Server_Reflection_Function) { + $func = $info->getName(); + $return = call_user_func_array($func, $params); + } elseif (($info instanceof IPF_Server_Reflection_Method) && $info->system) { + // System methods + $return = $info->invokeArgs($this, $params); + } elseif ($info instanceof IPF_Server_Reflection_Method) { + // Get class + $class = $info->getDeclaringClass()->getName(); + + if ('static' == $info->isStatic()) { + // for some reason, invokeArgs() does not work the same as + // invoke(), and expects the first argument to be an object. + // So, using a callback if the method is static. + $return = call_user_func_array(array($class, $info->getName()), $params); + } else { + // Object methods + try { + $object = $info->getDeclaringClass()->newInstance(); + } catch (Exception $e) { + throw new IPF_Exception('Error instantiating class ' . $class . ' to invoke method ' . $info->getName(), 621); + } + + $return = $info->invokeArgs($object, $params); + } + } else { + throw new IPF_Exception('Method missing implementation ' . get_class($info), 622); + } + + $response = new ReflectionClass($this->_responseClass); + return $response->newInstance($return); + } + + public function handle(IPF_XmlRpc_Request $request = null) + { + // Get request + if ((null === $request) && (null === ($request = $this->getRequest()))) { + $request = new IPF_XmlRpc_Request_Http(); + $request->setEncoding($this->getEncoding()); + } + + $this->setRequest($request); + + if ($request->isFault()) { + $response = $request->getFault(); + } else { + try { + $response = $this->_handle($request); + } catch (Exception $e) { + $response = $this->fault($e); + } + } + + // Set output encoding + $response->setEncoding($this->getEncoding()); + + return $response; + } + + public function setResponseClass($class) + { + if (class_exists($class)) { + $reflection = new ReflectionClass($class); + if ($reflection->isSubclassOf(new ReflectionClass('IPF_XmlRpc_Response'))) { + $this->_responseClass = $class; + return true; + } + } + + return false; + } + + public function getFunctions() + { + $return = array(); + foreach ($this->_methods as $method) { + if ($method instanceof IPF_Server_Reflection_Class + && ($method->system)) + { + continue; + } + + $return[] = $method; + } + + return $return; + } + + public function listMethods() + { + return array_keys($this->_table); + } + + public function methodHelp($method) + { + if (!isset($this->_table[$method])) { + throw new IPF_Exception('Method "' . $method . '"does not exist', 640); + } + + return $this->_table[$method]->getDescription(); + } + + public function methodSignature($method) + { + if (!isset($this->_table[$method])) { + throw new IPF_Exception('Method "' . $method . '"does not exist', 640); + } + $prototypes = $this->_table[$method]->getPrototypes(); + + $signatures = array(); + foreach ($prototypes as $prototype) { + $signature = array($prototype->getReturnType()); + foreach ($prototype->getParameters() as $parameter) { + $signature[] = $parameter->getType(); + } + $signatures[] = $signature; + } + + return $signatures; + } + + public function multicall($methods) + { + $responses = array(); + foreach ($methods as $method) { + $fault = false; + if (!is_array($method)) { + $fault = $this->fault('system.multicall expects each method to be a struct', 601); + } elseif (!isset($method['methodName'])) { + $fault = $this->fault('Missing methodName', 602); + } elseif (!isset($method['params'])) { + $fault = $this->fault('Missing params', 603); + } elseif (!is_array($method['params'])) { + $fault = $this->fault('Params must be an array', 604); + } else { + if ('system.multicall' == $method['methodName']) { + // don't allow recursive calls to multicall + $fault = $this->fault('Recursive system.multicall forbidden', 605); + } + } + + if (!$fault) { + try { + $request = new IPF_XmlRpc_Request(); + $request->setMethod($method['methodName']); + $request->setParams($method['params']); + $response = $this->_handle($request); + $responses[] = $response->getReturnValue(); + } catch (Exception $e) { + $fault = $this->fault($e); + } + } + + if ($fault) { + $responses[] = array( + 'faultCode' => $fault->getCode(), + 'faultString' => $fault->getMessage() + ); + } + } + return $responses; + } +} diff --git a/ipf/xmlrpc/server/cache.php b/ipf/xmlrpc/server/cache.php new file mode 100644 index 0000000..aa6c7e7 --- /dev/null +++ b/ipf/xmlrpc/server/cache.php @@ -0,0 +1,60 @@ +getFunctions(); + foreach ($methods as $name => $method) { + if ($method->system) { + unset($methods[$name]); + } + } + + // Store + if (0 === @file_put_contents($filename, serialize($methods))) { + return false; + } + + return true; + } + + public static function get($filename, IPF_XmlRpc_Server $server) + { + if (!is_string($filename) + || !file_exists($filename) + || !is_readable($filename)) + { + return false; + } + + if (false === ($dispatch = @file_get_contents($filename))) { + return false; + } + + if (false === ($dispatchArray = @unserialize($dispatch))) { + return false; + } + + $server->loadFunctions($dispatchArray); + + return true; + } + + public static function delete($filename) + { + if (is_string($filename) && file_exists($filename)) { + unlink($filename); + return true; + } + + return false; + } +} diff --git a/ipf/xmlrpc/server/fault.php b/ipf/xmlrpc/server/fault.php new file mode 100644 index 0000000..50db5ed --- /dev/null +++ b/ipf/xmlrpc/server/fault.php @@ -0,0 +1,95 @@ + true); + protected static $_observers = array(); + + public function __construct(Exception $e) + { + $this->_exception = $e; + $code = 404; + $message = 'Unknown error'; + $exceptionClass = get_class($e); + + foreach (array_keys(self::$_faultExceptionClasses) as $class) { + if ($e instanceof $class) { + $code = $e->getCode(); + $message = $e->getMessage(); + break; + } + } + + parent::__construct($code, $message); + + // Notify exception observers, if present + if (!empty(self::$_observers)) { + foreach (array_keys(self::$_observers) as $observer) { + call_user_func(array($observer, 'observe'), $this); + } + } + } + + public static function getInstance(Exception $e) + { + return new self($e); + } + + public static function attachFaultException($classes) + { + if (!is_array($classes)) { + $classes = (array) $classes; + } + + foreach ($classes as $class) { + if (is_string($class) && class_exists($class)) { + self::$_faultExceptionClasses[$class] = true; + } + } + } + + public static function detachFaultException($classes) + { + if (!is_array($classes)) { + $classes = (array) $classes; + } + + foreach ($classes as $class) { + if (is_string($class) && isset(self::$_faultExceptionClasses[$class])) { + unset(self::$_faultExceptionClasses[$class]); + } + } + } + + public static function attachObserver($class) + { + if (!is_string($class) + || !class_exists($class) + || !is_callable(array($class, 'observe'))) + { + return false; + } + + if (!isset(self::$_observers[$class])) { + self::$_observers[$class] = true; + } + + return true; + } + + public static function detachObserver($class) + { + if (!isset(self::$_observers[$class])) { + return false; + } + + unset(self::$_observers[$class]); + return true; + } + + public function getException() + { + return $this->_exception; + } +} diff --git a/ipf/xmlrpc/value.php b/ipf/xmlrpc/value.php new file mode 100644 index 0000000..5201bed --- /dev/null +++ b/ipf/xmlrpc/value.php @@ -0,0 +1,240 @@ +_type; + } + + abstract public function getValue(); + abstract public function saveXML(); + + public function getAsDOM() + { + if (!$this->_as_dom) { + $doc = new DOMDocument('1.0'); + $doc->loadXML($this->saveXML()); + $this->_as_dom = $doc->documentElement; + } + + return $this->_as_dom; + } + + protected function _stripXmlDeclaration(DOMDocument $dom) + { + return preg_replace('/<\?xml version="1.0"( encoding="[^\"]*")?\?>\n/u', '', $dom->saveXML()); + } + + public static function getXmlRpcValue($value, $type = self::AUTO_DETECT_TYPE) + { + switch ($type) { + case self::AUTO_DETECT_TYPE: + // Auto detect the XML-RPC native type from the PHP type of $value + return self::_phpVarToNativeXmlRpc($value); + + case self::XML_STRING: + // Parse the XML string given in $value and get the XML-RPC value in it + return self::_xmlStringToNativeXmlRpc($value); + + case self::XMLRPC_TYPE_I4: + // fall through to the next case + case self::XMLRPC_TYPE_INTEGER: + return new IPF_XmlRpc_Value_Integer($value); + + case self::XMLRPC_TYPE_DOUBLE: + return new IPF_XmlRpc_Value_Double($value); + + case self::XMLRPC_TYPE_BOOLEAN: + return new IPF_XmlRpc_Value_Boolean($value); + + case self::XMLRPC_TYPE_STRING: + return new IPF_XmlRpc_Value_String($value); + + case self::XMLRPC_TYPE_BASE64: + return new IPF_XmlRpc_Value_Base64($value); + + case self::XMLRPC_TYPE_NIL: + return new IPF_XmlRpc_Value_Nil(); + + case self::XMLRPC_TYPE_DATETIME: + return new IPF_XmlRpc_Value_DateTime($value); + + case self::XMLRPC_TYPE_ARRAY: + return new IPF_XmlRpc_Value_Array($value); + + case self::XMLRPC_TYPE_STRUCT: + return new IPF_XmlRpc_Value_Struct($value); + + default: + throw new IPF_Exception('Given type is not a '. __CLASS__ .' constant'); + } + } + + private static function _phpVarToNativeXmlRpc($value) + { + switch (gettype($value)) { + case 'object': + // Check to see if it's an XmlRpc value + if ($value instanceof IPF_XmlRpc_Value) { + return $value; + } + + // Otherwise, we convert the object into a struct + $value = get_object_vars($value); + // Break intentionally omitted + case 'array': + // Default native type for a PHP array (a simple numeric array) is 'array' + $obj = 'IPF_XmlRpc_Value_Array'; + + // Determine if this is an associative array + if (!empty($value) && is_array($value) && (array_keys($value) !== range(0, count($value) - 1))) { + $obj = 'IPF_XmlRpc_Value_Struct'; + } + return new $obj($value); + + case 'integer': + return new IPF_XmlRpc_Value_Integer($value); + + case 'double': + return new IPF_XmlRpc_Value_Double($value); + + case 'boolean': + return new IPF_XmlRpc_Value_Boolean($value); + + case 'NULL': + case 'null': + return new IPF_XmlRpc_Value_Nil(); + + case 'string': + // Fall through to the next case + default: + // If type isn't identified (or identified as string), it treated as string + return new IPF_XmlRpc_Value_String($value); + } + } + + private static function _xmlStringToNativeXmlRpc($simple_xml) + { + if (!$simple_xml instanceof SimpleXMLElement) { + try { + $simple_xml = @new SimpleXMLElement($simple_xml); + } catch (Exception $e) { + // The given string is not a valid XML + throw new IPF_Exception('Failed to create XML-RPC value from XML string: '.$e->getMessage(),$e->getCode()); + } + } + + // Get the key (tag name) and value from the simple xml object and convert the value to an XML-RPC native value + list($type, $value) = each($simple_xml); + if (!$type) { // If no type was specified, the default is string + $type = self::XMLRPC_TYPE_STRING; + } + + switch ($type) { + // All valid and known XML-RPC native values + case self::XMLRPC_TYPE_I4: + // Fall through to the next case + case self::XMLRPC_TYPE_INTEGER: + $xmlrpc_val = new IPF_XmlRpc_Value_Integer($value); + break; + case self::XMLRPC_TYPE_DOUBLE: + $xmlrpc_val = new IPF_XmlRpc_Value_Double($value); + break; + case self::XMLRPC_TYPE_BOOLEAN: + $xmlrpc_val = new IPF_XmlRpc_Value_Boolean($value); + break; + case self::XMLRPC_TYPE_STRING: + $xmlrpc_val = new IPF_XmlRpc_Value_String($value); + break; + case self::XMLRPC_TYPE_DATETIME: // The value should already be in a iso8601 format + $xmlrpc_val = new IPF_XmlRpc_Value_DateTime($value); + break; + case self::XMLRPC_TYPE_BASE64: // The value should already be base64 encoded + $xmlrpc_val = new IPF_XmlRpc_Value_Base64($value ,true); + break; + case self::XMLRPC_TYPE_NIL: // The value should always be NULL + $xmlrpc_val = new IPF_XmlRpc_Value_Nil(); + break; + case self::XMLRPC_TYPE_ARRAY: + // If the XML is valid, $value must be an SimpleXML element and contain the tag + if (!$value instanceof SimpleXMLElement) { + throw new IPF_Exception('XML string is invalid for XML-RPC native '. self::XMLRPC_TYPE_ARRAY .' type'); + } + + // PHP 5.2.4 introduced a regression in how empty($xml->value) + // returns; need to look for the item specifically + $data = null; + foreach ($value->children() as $key => $value) { + if ('data' == $key) { + $data = $value; + break; + } + } + + if (null === $data) { + throw new IPF_Exception('Invalid XML for XML-RPC native '. self::XMLRPC_TYPE_ARRAY .' type: ARRAY tag must contain DATA tag'); + } + $values = array(); + // Parse all the elements of the array from the XML string + // (simple xml element) to IPF_XmlRpc_Value objects + foreach ($data->value as $element) { + $values[] = self::_xmlStringToNativeXmlRpc($element); + } + $xmlrpc_val = new IPF_XmlRpc_Value_Array($values); + break; + case self::XMLRPC_TYPE_STRUCT: + // If the XML is valid, $value must be an SimpleXML + if ((!$value instanceof SimpleXMLElement)) { + throw new IPF_Exception('XML string is invalid for XML-RPC native '. self::XMLRPC_TYPE_STRUCT .' type'); + } + $values = array(); + // Parse all the memebers of the struct from the XML string + // (simple xml element) to IPF_XmlRpc_Value objects + foreach ($value->member as $member) { + // @todo? If a member doesn't have a tag, we don't add it to the struct + // Maybe we want to throw an exception here ? + if ((!$member->value instanceof SimpleXMLElement) || empty($member->value)) { + continue; + //throw new IPF_XmlRpc_Value_Exception('Member of the '. self::XMLRPC_TYPE_STRUCT .' XML-RPC native type must contain a VALUE tag'); + } + $values[(string)$member->name] = self::_xmlStringToNativeXmlRpc($member->value); + } + $xmlrpc_val = new IPF_XmlRpc_Value_Struct($values); + break; + default: + throw new IPF_Exception('Value type \''. $type .'\' parsed from the XML string is not a known XML-RPC native type'); + break; + } + $xmlrpc_val->_setXML($simple_xml->asXML()); + + return $xmlrpc_val; + } + + private function _setXML($xml) + { + $this->_as_xml = $xml; + } + +} + + diff --git a/ipf/xmlrpc/value/array.php b/ipf/xmlrpc/value/array.php new file mode 100644 index 0000000..ead3419 --- /dev/null +++ b/ipf/xmlrpc/value/array.php @@ -0,0 +1,33 @@ +_type = self::XMLRPC_TYPE_ARRAY; + parent::__construct($value); + } + + public function saveXML() + { + if (!$this->_as_xml) { // The XML code was not calculated yet + $dom = new DOMDocument('1.0'); + $value = $dom->appendChild($dom->createElement('value')); + $array = $value->appendChild($dom->createElement('array')); + $data = $array->appendChild($dom->createElement('data')); + + if (is_array($this->_value)) { + foreach ($this->_value as $val) { + /* @var $val IPF_XmlRpc_Value */ + $data->appendChild($dom->importNode($val->getAsDOM(), true)); + } + } + + $this->_as_dom = $value; + $this->_as_xml = $this->_stripXmlDeclaration($dom); + } + + return $this->_as_xml; + } +} + diff --git a/ipf/xmlrpc/value/base64.php b/ipf/xmlrpc/value/base64.php new file mode 100644 index 0000000..0583f9f --- /dev/null +++ b/ipf/xmlrpc/value/base64.php @@ -0,0 +1,36 @@ +_type = self::XMLRPC_TYPE_BASE64; + + $value = (string)$value; // Make sure this value is string + if (!$already_encoded) { + $value = base64_encode($value); // We encode it in base64 + } + $this->_value = $value; + } + + public function getValue() + { + return base64_decode($this->_value); + } + + public function saveXML() + { + if (! $this->_as_xml) { // The XML was not generated yet + $dom = new DOMDocument('1.0', 'UTF-8'); + $value = $dom->appendChild($dom->createElement('value')); + $type = $value->appendChild($dom->createElement($this->_type)); + $type->appendChild($dom->createTextNode($this->_value)); + + $this->_as_dom = $value; + $this->_as_xml = $this->_stripXmlDeclaration($dom); + } + + return $this->_as_xml; + } +} + diff --git a/ipf/xmlrpc/value/boolean.php b/ipf/xmlrpc/value/boolean.php new file mode 100644 index 0000000..c0cf52c --- /dev/null +++ b/ipf/xmlrpc/value/boolean.php @@ -0,0 +1,31 @@ +_type = self::XMLRPC_TYPE_BOOLEAN; + $this->_value = (int)(bool)$value; + } + + public function getValue() + { + return (bool)$this->_value; + } + + public function saveXML() + { + if (! $this->_as_xml) { // The XML was not generated yet + $dom = new DOMDocument('1.0', 'UTF-8'); + $value = $dom->appendChild($dom->createElement('value')); + $type = $value->appendChild($dom->createElement($this->_type)); + $type->appendChild($dom->createTextNode($this->_value)); + + $this->_as_dom = $value; + $this->_as_xml = $this->_stripXmlDeclaration($dom); + } + + return $this->_as_xml; + } +} + diff --git a/ipf/xmlrpc/value/collection.php b/ipf/xmlrpc/value/collection.php new file mode 100644 index 0000000..a720f10 --- /dev/null +++ b/ipf/xmlrpc/value/collection.php @@ -0,0 +1,32 @@ + $value) { + // If the elements of the given array are not IPF_XmlRpc_Value objects, + // we need to convert them as such (using auto-detection from PHP value) + if (!$value instanceof parent) { + $value = self::getXmlRpcValue($value, self::AUTO_DETECT_TYPE); + } + $this->_value[$key] = $value; + } + } + + public function getValue() + { + $values = (array)$this->_value; + foreach ($values as $key => $value) { + /* @var $value IPF_XmlRpc_Value */ + if (!$value instanceof parent) { + throw new IPF_Exception('Values of '. get_class($this) .' type must be IPF_XmlRpc_Value objects'); + } + $values[$key] = $value->getValue(); + } + return $values; + } + +} + diff --git a/ipf/xmlrpc/value/datetime.php b/ipf/xmlrpc/value/datetime.php new file mode 100644 index 0000000..569cd75 --- /dev/null +++ b/ipf/xmlrpc/value/datetime.php @@ -0,0 +1,33 @@ +_type = self::XMLRPC_TYPE_DATETIME; + + // If the value is not numeric, we try to convert it to a timestamp (using the strtotime function) + if (is_numeric($value)) { // The value is numeric, we make sure it is an integer + $value = (int)$value; + } else { + $value = strtotime($value); + if ($value === false || $value == -1) { // cannot convert the value to a timestamp + throw new IPF_Exception('Cannot convert given value \''. $value .'\' to a timestamp'); + } + } + $value = date('c', $value); // Convert the timestamp to iso8601 format + + // Strip out TZ information and dashes + $value = preg_replace('/(\+|-)\d{2}:\d{2}$/', '', $value); + $value = str_replace('-', '', $value); + + $this->_value = $value; + } + + public function getValue() + { + return $this->_value; + } + +} + diff --git a/ipf/xmlrpc/value/double.php b/ipf/xmlrpc/value/double.php new file mode 100644 index 0000000..e0a8536 --- /dev/null +++ b/ipf/xmlrpc/value/double.php @@ -0,0 +1,17 @@ +_type = self::XMLRPC_TYPE_DOUBLE; + $this->_value = sprintf('%f',(float)$value); // Make sure this value is float (double) and without the scientific notation + } + + public function getValue() + { + return (float)$this->_value; + } + +} + diff --git a/ipf/xmlrpc/value/integer.php b/ipf/xmlrpc/value/integer.php new file mode 100644 index 0000000..5de4c8d --- /dev/null +++ b/ipf/xmlrpc/value/integer.php @@ -0,0 +1,17 @@ +_type = self::XMLRPC_TYPE_INTEGER; + $this->_value = (int)$value; // Make sure this value is integer + } + + public function getValue() + { + return $this->_value; + } + +} + diff --git a/ipf/xmlrpc/value/nil.php b/ipf/xmlrpc/value/nil.php new file mode 100644 index 0000000..e04c670 --- /dev/null +++ b/ipf/xmlrpc/value/nil.php @@ -0,0 +1,30 @@ +_type = self::XMLRPC_TYPE_NIL; + $this->_value = null; + } + + public function getValue() + { + return null; + } + + public function saveXML() + { + if (! $this->_as_xml) { // The XML was not generated yet + $dom = new DOMDocument('1.0', 'UTF-8'); + $value = $dom->appendChild($dom->createElement('value')); + $type = $value->appendChild($dom->createElement($this->_type)); + + $this->_as_dom = $value; + $this->_as_xml = $this->_stripXmlDeclaration($dom); + } + + return $this->_as_xml; + } +} + diff --git a/ipf/xmlrpc/value/scalar.php b/ipf/xmlrpc/value/scalar.php new file mode 100644 index 0000000..a950fb5 --- /dev/null +++ b/ipf/xmlrpc/value/scalar.php @@ -0,0 +1,20 @@ +_as_xml) { // The XML code was not calculated yet + $dom = new DOMDocument('1.0'); + $value = $dom->appendChild($dom->createElement('value')); + $type = $value->appendChild($dom->createElement($this->_type)); + $type->appendChild($dom->createTextNode($this->getValue())); + + $this->_as_dom = $value; + $this->_as_xml = $this->_stripXmlDeclaration($dom); + } + + return $this->_as_xml; + } +} + diff --git a/ipf/xmlrpc/value/string.php b/ipf/xmlrpc/value/string.php new file mode 100644 index 0000000..635293a --- /dev/null +++ b/ipf/xmlrpc/value/string.php @@ -0,0 +1,24 @@ +_type = self::XMLRPC_TYPE_STRING; + + // Make sure this value is string and all XML characters are encoded + $this->_value = $this->_xml_entities($value); + } + + public function getValue() + { + return html_entity_decode($this->_value, ENT_QUOTES, 'UTF-8'); + } + + private function _xml_entities($str) + { + return htmlentities($str, ENT_QUOTES, 'UTF-8'); + } + +} + diff --git a/ipf/xmlrpc/value/struct.php b/ipf/xmlrpc/value/struct.php new file mode 100644 index 0000000..f6ed6ce --- /dev/null +++ b/ipf/xmlrpc/value/struct.php @@ -0,0 +1,34 @@ +_type = self::XMLRPC_TYPE_STRUCT; + parent::__construct($value); + } + + public function saveXML() + { + if (!$this->_as_xml) { // The XML code was not calculated yet + $dom = new DOMDocument('1.0'); + $value = $dom->appendChild($dom->createElement('value')); + $struct = $value->appendChild($dom->createElement('struct')); + + if (is_array($this->_value)) { + foreach ($this->_value as $name => $val) { + /* @var $val IPF_XmlRpc_Value */ + $member = $struct->appendChild($dom->createElement('member')); + $member->appendChild($dom->createElement('name', $name)); + $member->appendChild($dom->importNode($val->getAsDOM(), 1)); + } + } + + $this->_as_dom = $value; + $this->_as_xml = $this->_stripXmlDeclaration($dom); + } + + return $this->_as_xml; + } +} +