From 89c50a2a7856c5750d9badd575e937d498478ccf Mon Sep 17 00:00:00 2001 From: Colin Smith Date: Mon, 21 Mar 2005 21:10:29 +0000 Subject: Add pure PHP XMLRPC library from incutio.com. Using this (as opposed to the official XMLRPC-epi extension) means we will not need to include libiconv and expat with only a minor speed hit. --- etc/inc/xmlrpc.inc | 815 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 815 insertions(+) create mode 100644 etc/inc/xmlrpc.inc (limited to 'etc') diff --git a/etc/inc/xmlrpc.inc b/etc/inc/xmlrpc.inc new file mode 100644 index 0000000..7c03712 --- /dev/null +++ b/etc/inc/xmlrpc.inc @@ -0,0 +1,815 @@ + htmlspecialchars) + Site: http://scripts.incutio.com/xmlrpc/ + Made available under the Artistic License: http://www.opensource.org/licenses/artistic-license.php +*/ + +class IXR_Value { + var $data; + var $type; + function IXR_Value ($data, $type = false) { + $this->data = $data; + if (!$type) { + $type = $this->calculateType(); + } + $this->type = $type; + if ($type == 'struct') { + /* Turn all the values in the array in to new IXR_Value objects */ + foreach ($this->data as $key => $value) { + $this->data[$key] = new IXR_Value($value); + } + } + if ($type == 'array') { + for ($i = 0, $j = count($this->data); $i < $j; $i++) { + $this->data[$i] = new IXR_Value($this->data[$i]); + } + } + } + function calculateType() { + if ($this->data === true || $this->data === false) { + return 'boolean'; + } + if (is_integer($this->data)) { + return 'int'; + } + if (is_double($this->data)) { + return 'double'; + } + // Deal with IXR object types base64 and date + if (is_object($this->data) && is_a($this->data, 'IXR_Date')) { + return 'date'; + } + if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) { + return 'base64'; + } + // If it is a normal PHP object convert it in to a struct + if (is_object($this->data)) { + + $this->data = get_object_vars($this->data); + return 'struct'; + } + if (!is_array($this->data)) { + return 'string'; + } + /* We have an array - is it an array or a struct ? */ + if ($this->isStruct($this->data)) { + return 'struct'; + } else { + return 'array'; + } + } + function getXml() { + /* Return XML for this value */ + switch ($this->type) { + case 'boolean': + return ''.(($this->data) ? '1' : '0').''; + break; + case 'int': + return ''.$this->data.''; + break; + case 'double': + return ''.$this->data.''; + break; + case 'string': + return ''.htmlspecialchars($this->data).''; + break; + case 'array': + $return = ''."\n"; + foreach ($this->data as $item) { + $return .= ' '.$item->getXml()."\n"; + } + $return .= ''; + return $return; + break; + case 'struct': + $return = ''."\n"; + foreach ($this->data as $name => $value) { + $return .= " $name"; + $return .= $value->getXml()."\n"; + } + $return .= ''; + return $return; + break; + case 'date': + case 'base64': + return $this->data->getXml(); + break; + } + return false; + } + function isStruct($array) { + /* Nasty function to check if an array is a struct or not */ + $expected = 0; + foreach ($array as $key => $value) { + if ((string)$key != (string)$expected) { + return true; + } + $expected++; + } + return false; + } +} + + +class IXR_Message { + var $message; + var $messageType; // methodCall / methodResponse / fault + var $faultCode; + var $faultString; + var $methodName; + var $params; + // Current variable stacks + var $_arraystructs = array(); // The stack used to keep track of the current array/struct + var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array + var $_currentStructName = array(); // A stack as well + var $_param; + var $_value; + var $_currentTag; + var $_currentTagContents; + // The XML parser + var $_parser; + function IXR_Message ($message) { + $this->message = $message; + } + function parse() { + // first remove the XML declaration + $this->message = preg_replace('/<\?xml(.*)?\?'.'>/', '', $this->message); + if (trim($this->message) == '') { + return false; + } + $this->_parser = xml_parser_create(); + // Set XML parser to take the case of tags in to account + xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false); + // Set XML parser callback functions + xml_set_object($this->_parser, $this); + xml_set_element_handler($this->_parser, 'tag_open', 'tag_close'); + xml_set_character_data_handler($this->_parser, 'cdata'); + if (!xml_parse($this->_parser, $this->message)) { + /* die(sprintf('XML error: %s at line %d', + xml_error_string(xml_get_error_code($this->_parser)), + xml_get_current_line_number($this->_parser))); */ + return false; + } + xml_parser_free($this->_parser); + // Grab the error messages, if any + if ($this->messageType == 'fault') { + $this->faultCode = $this->params[0]['faultCode']; + $this->faultString = $this->params[0]['faultString']; + } + return true; + } + function tag_open($parser, $tag, $attr) { + $this->currentTag = $tag; + switch($tag) { + case 'methodCall': + case 'methodResponse': + case 'fault': + $this->messageType = $tag; + break; + /* Deal with stacks of arrays and structs */ + case 'data': // data is to all intents and puposes more interesting than array + $this->_arraystructstypes[] = 'array'; + $this->_arraystructs[] = array(); + break; + case 'struct': + $this->_arraystructstypes[] = 'struct'; + $this->_arraystructs[] = array(); + break; + } + } + function cdata($parser, $cdata) { + $this->_currentTagContents .= $cdata; + } + function tag_close($parser, $tag) { + $valueFlag = false; + switch($tag) { + case 'int': + case 'i4': + $value = (int)trim($this->_currentTagContents); + $this->_currentTagContents = ''; + $valueFlag = true; + break; + case 'double': + $value = (double)trim($this->_currentTagContents); + $this->_currentTagContents = ''; + $valueFlag = true; + break; + case 'string': + $value = (string)trim($this->_currentTagContents); + $this->_currentTagContents = ''; + $valueFlag = true; + break; + case 'dateTime.iso8601': + $value = new IXR_Date(trim($this->_currentTagContents)); + // $value = $iso->getTimestamp(); + $this->_currentTagContents = ''; + $valueFlag = true; + break; + case 'value': + // "If no type is indicated, the type is string." + if (trim($this->_currentTagContents) != '') { + $value = (string)$this->_currentTagContents; + $this->_currentTagContents = ''; + $valueFlag = true; + } + break; + case 'boolean': + $value = (boolean)trim($this->_currentTagContents); + $this->_currentTagContents = ''; + $valueFlag = true; + break; + case 'base64': + $value = base64_decode($this->_currentTagContents); + $this->_currentTagContents = ''; + $valueFlag = true; + break; + /* Deal with stacks of arrays and structs */ + case 'data': + case 'struct': + $value = array_pop($this->_arraystructs); + array_pop($this->_arraystructstypes); + $valueFlag = true; + break; + case 'member': + array_pop($this->_currentStructName); + break; + case 'name': + $this->_currentStructName[] = trim($this->_currentTagContents); + $this->_currentTagContents = ''; + break; + case 'methodName': + $this->methodName = trim($this->_currentTagContents); + $this->_currentTagContents = ''; + break; + } + if ($valueFlag) { + /* + if (!is_array($value) && !is_object($value)) { + $value = trim($value); + } + */ + if (count($this->_arraystructs) > 0) { + // Add value to struct or array + if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') { + // Add to struct + $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value; + } else { + // Add to array + $this->_arraystructs[count($this->_arraystructs)-1][] = $value; + } + } else { + // Just add as a paramater + $this->params[] = $value; + } + } + } +} + + +class IXR_Server { + var $data; + var $callbacks = array(); + var $message; + var $capabilities; + function IXR_Server($callbacks = false, $data = false) { + $this->setCapabilities(); + if ($callbacks) { + $this->callbacks = $callbacks; + } + $this->setCallbacks(); + $this->serve($data); + } + function serve($data = false) { + if (!$data) { + global $HTTP_RAW_POST_DATA; + if (!$HTTP_RAW_POST_DATA) { + die('XML-RPC server accepts POST requests only.'); + } + $data = $HTTP_RAW_POST_DATA; + } + $this->message = new IXR_Message($data); + if (!$this->message->parse()) { + $this->error(-32700, 'parse error. not well formed'); + } + if ($this->message->messageType != 'methodCall') { + $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall'); + } + $result = $this->call($this->message->methodName, $this->message->params); + // Is the result an error? + if (is_a($result, 'IXR_Error')) { + $this->error($result); + } + // Encode the result + $r = new IXR_Value($result); + $resultxml = $r->getXml(); + // Create the XML + $xml = << + + + + $resultxml + + + + + +EOD; + // Send it + $this->output($xml); + } + function call($methodname, $args) { + if (!$this->hasMethod($methodname)) { + return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.'); + } + $method = $this->callbacks[$methodname]; + // Perform the callback and send the response + if (count($args) == 1) { + // If only one paramater just send that instead of the whole array + $args = $args[0]; + } + // Are we dealing with a function or a method? + if (substr($method, 0, 5) == 'this:') { + // It's a class method - check it exists + $method = substr($method, 5); + if (!method_exists($this, $method)) { + return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.'); + } + // Call the method + $result = $this->$method($args); + } else { + // It's a function - does it exist? + if (!function_exists($method)) { + return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.'); + } + // Call the function + $result = $method($args); + } + return $result; + } + + function error($error, $message = false) { + // Accepts either an error object or an error code and message + if ($message && !is_object($error)) { + $error = new IXR_Error($error, $message); + } + $this->output($error->getXml()); + } + function output($xml) { + $xml = ''."\n".$xml; + $length = strlen($xml); + header('Connection: close'); + header('Content-Length: '.$length); + header('Content-Type: text/xml'); + header('Date: '.date('r')); + echo $xml; + exit; + } + function hasMethod($method) { + return in_array($method, array_keys($this->callbacks)); + } + function setCapabilities() { + // Initialises capabilities array + $this->capabilities = array( + 'xmlrpc' => array( + 'specUrl' => 'http://www.xmlrpc.com/spec', + 'specVersion' => 1 + ), + 'faults_interop' => array( + 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php', + 'specVersion' => 20010516 + ), + 'system.multicall' => array( + 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208', + 'specVersion' => 1 + ), + ); + } + function getCapabilities($args) { + return $this->capabilities; + } + function setCallbacks() { + $this->callbacks['system.getCapabilities'] = 'this:getCapabilities'; + $this->callbacks['system.listMethods'] = 'this:listMethods'; + $this->callbacks['system.multicall'] = 'this:multiCall'; + } + function listMethods($args) { + // Returns a list of methods - uses array_reverse to ensure user defined + // methods are listed before server defined methods + return array_reverse(array_keys($this->callbacks)); + } + function multiCall($methodcalls) { + // See http://www.xmlrpc.com/discuss/msgReader$1208 + $return = array(); + foreach ($methodcalls as $call) { + $method = $call['methodName']; + $params = $call['params']; + if ($method == 'system.multicall') { + $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden'); + } else { + $result = $this->call($method, $params); + } + if (is_a($result, 'IXR_Error')) { + $return[] = array( + 'faultCode' => $result->code, + 'faultString' => $result->message + ); + } else { + $return[] = array($result); + } + } + return $return; + } +} + +class IXR_Request { + var $method; + var $args; + var $xml; + function IXR_Request($method, $args) { + $this->method = $method; + $this->args = $args; + $this->xml = << + +{$this->method} + + +EOD; + foreach ($this->args as $arg) { + $this->xml .= ''; + $v = new IXR_Value($arg); + $this->xml .= $v->getXml(); + $this->xml .= "\n"; + } + $this->xml .= ''; + } + function getLength() { + return strlen($this->xml); + } + function getXml() { + return $this->xml; + } +} + + +class IXR_Client { + var $server; + var $port; + var $path; + var $useragent; + var $response; + var $message = false; + var $debug = false; + // Storage place for an error message + var $error = false; + function IXR_Client($server, $path = false, $port = 80) { + if (!$path) { + // Assume we have been given a URL instead + $bits = parse_url($server); + $this->server = $bits['host']; + $this->port = isset($bits['port']) ? $bits['port'] : 80; + $this->path = isset($bits['path']) ? $bits['path'] : '/'; + // Make absolutely sure we have a path + if (!$this->path) { + $this->path = '/'; + } + } else { + $this->server = $server; + $this->path = $path; + $this->port = $port; + } + $this->useragent = 'The Incutio XML-RPC PHP Library'; + } + function query() { + $args = func_get_args(); + $method = array_shift($args); + $request = new IXR_Request($method, $args); + $length = $request->getLength(); + $xml = $request->getXml(); + $r = "\r\n"; + $request = "POST {$this->path} HTTP/1.0$r"; + $request .= "Host: {$this->server}$r"; + $request .= "Content-Type: text/xml$r"; + $request .= "User-Agent: {$this->useragent}$r"; + $request .= "Content-length: {$length}$r$r"; + $request .= $xml; + // Now send the request + if ($this->debug) { + echo '
'.htmlspecialchars($request)."\n
\n\n"; + } + $fp = @fsockopen($this->server, $this->port); + if (!$fp) { + $this->error = new IXR_Error(-32300, 'transport error - could not open socket'); + return false; + } + fputs($fp, $request); + $contents = ''; + $gotFirstLine = false; + $gettingHeaders = true; + while (!feof($fp)) { + $line = fgets($fp, 4096); + if (!$gotFirstLine) { + // Check line for '200' + if (strstr($line, '200') === false) { + $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200'); + return false; + } + $gotFirstLine = true; + } + if (trim($line) == '') { + $gettingHeaders = false; + } + if (!$gettingHeaders) { + $contents .= trim($line)."\n"; + } + } + if ($this->debug) { + echo '
'.htmlspecialchars($contents)."\n
\n\n"; + } + // Now parse what we've got back + $this->message = new IXR_Message($contents); + if (!$this->message->parse()) { + // XML error + $this->error = new IXR_Error(-32700, 'parse error. not well formed'); + return false; + } + // Is the message a fault? + if ($this->message->messageType == 'fault') { + $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString); + return false; + } + // Message must be OK + return true; + } + function getResponse() { + // methodResponses can only have one param - return that + return $this->message->params[0]; + } + function isError() { + return (is_object($this->error)); + } + function getErrorCode() { + return $this->error->code; + } + function getErrorMessage() { + return $this->error->message; + } +} + + +class IXR_Error { + var $code; + var $message; + function IXR_Error($code, $message) { + $this->code = $code; + $this->message = $message; + } + function getXml() { + $xml = << + + + + + faultCode + {$this->code} + + + faultString + {$this->message} + + + + + + +EOD; + return $xml; + } +} + + +class IXR_Date { + var $year; + var $month; + var $day; + var $hour; + var $minute; + var $second; + function IXR_Date($time) { + // $time can be a PHP timestamp or an ISO one + if (is_numeric($time)) { + $this->parseTimestamp($time); + } else { + $this->parseIso($time); + } + } + function parseTimestamp($timestamp) { + $this->year = date('Y', $timestamp); + $this->month = date('Y', $timestamp); + $this->day = date('Y', $timestamp); + $this->hour = date('H', $timestamp); + $this->minute = date('i', $timestamp); + $this->second = date('s', $timestamp); + } + function parseIso($iso) { + $this->year = substr($iso, 0, 4); + $this->month = substr($iso, 4, 2); + $this->day = substr($iso, 6, 2); + $this->hour = substr($iso, 9, 2); + $this->minute = substr($iso, 12, 2); + $this->second = substr($iso, 15, 2); + } + function getIso() { + return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second; + } + function getXml() { + return ''.$this->getIso().''; + } + function getTimestamp() { + return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year); + } +} + + +class IXR_Base64 { + var $data; + function IXR_Base64($data) { + $this->data = $data; + } + function getXml() { + return ''.base64_encode($this->data).''; + } +} + + +class IXR_IntrospectionServer extends IXR_Server { + var $signatures; + var $help; + function IXR_IntrospectionServer() { + $this->setCallbacks(); + $this->setCapabilities(); + $this->capabilities['introspection'] = array( + 'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html', + 'specVersion' => 1 + ); + $this->addCallback( + 'system.methodSignature', + 'this:methodSignature', + array('array', 'string'), + 'Returns an array describing the return type and required parameters of a method' + ); + $this->addCallback( + 'system.getCapabilities', + 'this:getCapabilities', + array('struct'), + 'Returns a struct describing the XML-RPC specifications supported by this server' + ); + $this->addCallback( + 'system.listMethods', + 'this:listMethods', + array('array'), + 'Returns an array of available methods on this server' + ); + $this->addCallback( + 'system.methodHelp', + 'this:methodHelp', + array('string', 'string'), + 'Returns a documentation string for the specified method' + ); + } + function addCallback($method, $callback, $args, $help) { + $this->callbacks[$method] = $callback; + $this->signatures[$method] = $args; + $this->help[$method] = $help; + } + function call($methodname, $args) { + // Make sure it's in an array + if ($args && !is_array($args)) { + $args = array($args); + } + // Over-rides default call method, adds signature check + if (!$this->hasMethod($methodname)) { + return new IXR_Error(-32601, 'server error. requested method "'.$this->message->methodName.'" not specified.'); + } + $method = $this->callbacks[$methodname]; + $signature = $this->signatures[$methodname]; + $returnType = array_shift($signature); + // Check the number of arguments + if (count($args) != count($signature)) { + // print 'Num of args: '.count($args).' Num in signature: '.count($signature); + return new IXR_Error(-32602, 'server error. wrong number of method parameters'); + } + // Check the argument types + $ok = true; + $argsbackup = $args; + for ($i = 0, $j = count($args); $i < $j; $i++) { + $arg = array_shift($args); + $type = array_shift($signature); + switch ($type) { + case 'int': + case 'i4': + if (is_array($arg) || !is_int($arg)) { + $ok = false; + } + break; + case 'base64': + case 'string': + if (!is_string($arg)) { + $ok = false; + } + break; + case 'boolean': + if ($arg !== false && $arg !== true) { + $ok = false; + } + break; + case 'float': + case 'double': + if (!is_float($arg)) { + $ok = false; + } + break; + case 'date': + case 'dateTime.iso8601': + if (!is_a($arg, 'IXR_Date')) { + $ok = false; + } + break; + } + if (!$ok) { + return new IXR_Error(-32602, 'server error. invalid method parameters'); + } + } + // It passed the test - run the "real" method call + return parent::call($methodname, $argsbackup); + } + function methodSignature($method) { + if (!$this->hasMethod($method)) { + return new IXR_Error(-32601, 'server error. requested method "'.$method.'" not specified.'); + } + // We should be returning an array of types + $types = $this->signatures[$method]; + $return = array(); + foreach ($types as $type) { + switch ($type) { + case 'string': + $return[] = 'string'; + break; + case 'int': + case 'i4': + $return[] = 42; + break; + case 'double': + $return[] = 3.1415; + break; + case 'dateTime.iso8601': + $return[] = new IXR_Date(time()); + break; + case 'boolean': + $return[] = true; + break; + case 'base64': + $return[] = new IXR_Base64('base64'); + break; + case 'array': + $return[] = array('array'); + break; + case 'struct': + $return[] = array('struct' => 'struct'); + break; + } + } + return $return; + } + function methodHelp($method) { + return $this->help[$method]; + } +} + + +class IXR_ClientMulticall extends IXR_Client { + var $calls = array(); + function IXR_ClientMulticall($server, $path = false, $port = 80) { + parent::IXR_Client($server, $path, $port); + $this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)'; + } + function addCall() { + $args = func_get_args(); + $methodName = array_shift($args); + $struct = array( + 'methodName' => $methodName, + 'params' => $args + ); + $this->calls[] = $struct; + } + function query() { + // Prepare multicall, then call the parent::query() method + return parent::query('system.multicall', $this->calls); + } +} +?> -- cgit v1.1