From b6681659a7cabd3599f6a7040aa06fa75e4be052 Mon Sep 17 00:00:00 2001 From: gggeek Date: Sat, 9 Jan 2021 18:34:49 +0000 Subject: [PATCH] comments --- src/Encoder.php.bak | 339 +++++++++++++++++++++++++++++++++++++++ src/Helper/Charset.php | 11 +- src/Helper/XMLParser.php | 2 + src/Request.php | 1 + src/Server.php | 1 + 5 files changed, 351 insertions(+), 3 deletions(-) create mode 100644 src/Encoder.php.bak diff --git a/src/Encoder.php.bak b/src/Encoder.php.bak new file mode 100644 index 0000000..ab4311c --- /dev/null +++ b/src/Encoder.php.bak @@ -0,0 +1,339 @@ +kindOf()) { + case 'scalar': + if (in_array('extension_api', $options)) { + $val = reset($xmlrpcVal->me); + $typ = key($xmlrpcVal->me); + switch ($typ) { + case 'dateTime.iso8601': + $xmlrpcVal = array( + 'xmlrpc_type' => 'datetime', + 'scalar' => $val, + 'timestamp' => \PhpXmlRpc\Helper\Date::iso8601Decode($val) + ); + return (object)$xmlrpcVal; + case 'base64': + $xmlrpcVal = array( + 'xmlrpc_type' => 'base64', + 'scalar' => $val + ); + return (object)$xmlrpcVal; + default: + return $xmlrpcVal->scalarval(); + } + } + if (in_array('dates_as_objects', $options) && $xmlrpcVal->scalartyp() == 'dateTime.iso8601') { + // we return a Datetime object instead of a string since now the constructor of xmlrpc value accepts + // safely strings, ints and datetimes, we cater to all 3 cases here + $out = $xmlrpcVal->scalarval(); + if (is_string($out)) { + $out = strtotime($out); + } + if (is_int($out)) { + $result = new \DateTime(); + $result->setTimestamp($out); + + return $result; + } elseif (is_a($out, 'DateTimeInterface')) { + return $out; + } + } + return $xmlrpcVal->scalarval(); + + case 'array': + $arr = array(); + foreach($xmlrpcVal as $value) { + $arr[] = $this->decode($value, $options); + } + return $arr; + + case 'struct': + // If user said so, try to rebuild php objects for specific struct vals. + /// @todo should we raise a warning for class not found? + // shall we check for proper subclass of xmlrpc value instead of presence of _php_class to detect + // what we can do? + if (in_array('decode_php_objs', $options) && $xmlrpcVal->_php_class != '' + && class_exists($xmlrpcVal->_php_class) + ) { + $obj = @new $xmlrpcVal->_php_class(); + foreach ($xmlrpcVal as $key => $value) { + $obj->$key = $this->decode($value, $options); + } + return $obj; + } else { + $arr = array(); + foreach ($xmlrpcVal as $key => $value) { + $arr[$key] = $this->decode($value, $options); + } + return $arr; + } + + case 'msg': + $paramCount = $xmlrpcVal->getNumParams(); + $arr = array(); + for ($i = 0; $i < $paramCount; $i++) { + $arr[] = $this->decode($xmlrpcVal->getParam($i), $options); + } + return $arr; + + /// @todo throw on unsupported type + } + } + + /** + * Takes native php types and encodes them into xmlrpc PHP object format. + * It will not re-encode xmlrpc value objects. + * + * Feature creep -- could support more types via optional type argument + * (string => datetime support has been added, ??? => base64 not yet) + * + * If given a proper options parameter, php object instances will be encoded into 'special' xmlrpc values, that can + * later be decoded into php objects by calling php_xmlrpc_decode() with a corresponding option + * + * @author Dan Libby (dan@libby.com) + * + * @param mixed $phpVal the value to be converted into an xmlrpc value object + * @param array $options can include 'encode_php_objs', 'auto_dates', 'null_extension' or 'extension_api' + * + * @return Value + */ + public function encode($phpVal, $options = array()) + { + $type = gettype($phpVal); + switch ($type) { + case 'string': + /// @todo should we be stricter in the accepted dates (ie. reject more of invalid days & times)? + if (in_array('auto_dates', $options) && preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $phpVal)) { + $xmlrpcVal = new Value($phpVal, Value::$xmlrpcDateTime); + } else { + $xmlrpcVal = new Value($phpVal, Value::$xmlrpcString); + } + break; + case 'integer': + $xmlrpcVal = new Value($phpVal, Value::$xmlrpcInt); + break; + case 'double': + $xmlrpcVal = new Value($phpVal, Value::$xmlrpcDouble); + break; + // Add support for encoding/decoding of booleans, since they are supported in PHP + case 'boolean': + $xmlrpcVal = new Value($phpVal, Value::$xmlrpcBoolean); + break; + case 'array': + // PHP arrays can be encoded to either xmlrpc structs or arrays, depending on whether they are hashes + // or plain 0..n integer indexed + // A shorter one-liner would be + // $tmp = array_diff(array_keys($phpVal), range(0, count($phpVal)-1)); + // but execution time skyrockets! + $j = 0; + $arr = array(); + $ko = false; + foreach ($phpVal as $key => $val) { + $arr[$key] = $this->encode($val, $options); + if (!$ko && $key !== $j) { + $ko = true; + } + $j++; + } + if ($ko) { + $xmlrpcVal = new Value($arr, Value::$xmlrpcStruct); + } else { + $xmlrpcVal = new Value($arr, Value::$xmlrpcArray); + } + break; + case 'object': + if (is_a($phpVal, 'PhpXmlRpc\Value')) { + $xmlrpcVal = $phpVal; + } elseif (is_a($phpVal, 'DateTimeInterface')) { + $xmlrpcVal = new Value($phpVal->format('Ymd\TH:i:s'), Value::$xmlrpcDateTime); + } elseif (in_array('extension_api', $options) && $phpVal instanceof \stdClass && isset($phpVal->xmlrpc_type)) { + // Handle the 'pre-converted' base64 and datetime values + if (isset($phpVal->scalar)) { + switch ($phpVal->xmlrpc_type) { + case 'base64': + $xmlrpcVal = new Value($phpVal->scalar, Value::$xmlrpcBase64); + break; + case 'datetime': + $xmlrpcVal = new Value($phpVal->scalar, Value::$xmlrpcDateTime); + break; + default: + $xmlrpcVal = new Value(); + } + } else { + $xmlrpcVal = new Value(); + } + + } else { + $arr = array(); + foreach($phpVal as $k => $v) { + $arr[$k] = $this->encode($v, $options); + } + $xmlrpcVal = new Value($arr, Value::$xmlrpcStruct); + if (in_array('encode_php_objs', $options)) { + // let's save original class name into xmlrpc value: + // might be useful later on... + $xmlrpcVal->_php_class = get_class($phpVal); + } + } + break; + case 'NULL': + if (in_array('extension_api', $options)) { + $xmlrpcVal = new Value('', Value::$xmlrpcString); + } elseif (in_array('null_extension', $options)) { + $xmlrpcVal = new Value('', Value::$xmlrpcNull); + } else { + $xmlrpcVal = new Value(); + } + break; + case 'resource': + if (in_array('extension_api', $options)) { + $xmlrpcVal = new Value((int)$phpVal, Value::$xmlrpcInt); + } else { + $xmlrpcVal = new Value(); + } + break; + // catch "user function", "unknown type" + default: + // giancarlo pinerolo + // it has to return an empty object in case, not a boolean. + $xmlrpcVal = new Value(); + break; + } + + return $xmlrpcVal; + } + + /** + * Convert the xml representation of a method response, method request or single + * xmlrpc value into the appropriate object (a.k.a. deserialize). + * + * @todo is this a good name/class for this method? It does something quite different from 'decode' after all + * (returning objects vs returns plain php values)... In fact it belongs rather to a Parser class + * + * @param string $xmlVal + * @param array $options + * + * @return Value|Request|Response|false false on error, or an instance of either Value, Request or Response + */ + public function decodeXml($xmlVal, $options = array()) + { + // 'guestimate' encoding + $valEncoding = XMLParser::guessEncoding('', $xmlVal); + if ($valEncoding != '') { + + // Since parsing will fail if + // - charset is not specified in the xml prologue, + // - the encoding is not UTF8 and + // - there are non-ascii chars in the text, + // we try to work round that... + // The following code might be better for mb_string enabled installs, but makes the lib about 200% slower... + //if (!is_valid_charset($valEncoding, array('UTF-8')) + if (!in_array($valEncoding, array('UTF-8', 'US-ASCII')) && !XMLParser::hasEncoding($xmlVal)) { + if ($valEncoding == 'ISO-8859-1') { + $xmlVal = utf8_encode($xmlVal); + } else { + if (extension_loaded('mbstring')) { + $xmlVal = mb_convert_encoding($xmlVal, 'UTF-8', $valEncoding); + } else { + Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': invalid charset encoding of xml text: ' . $valEncoding); + } + } + } + } + + // What if internal encoding is not in one of the 3 allowed? We use the broadest one, ie. utf8! + if (!in_array(PhpXmlRpc::$xmlrpc_internalencoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) { + /// @todo emit a warning + $parserOptions = array(XML_OPTION_TARGET_ENCODING => 'UTF-8'); + } else { + $parserOptions = array(XML_OPTION_TARGET_ENCODING => PhpXmlRpc::$xmlrpc_internalencoding); + } + + $xmlRpcParser = new XMLParser($parserOptions); + $xmlRpcParser->parse($xmlVal, XMLParser::RETURN_XMLRPCVALS, XMLParser::ACCEPT_REQUEST | XMLParser::ACCEPT_RESPONSE | XMLParser::ACCEPT_VALUE | XMLParser::ACCEPT_FAULT); + + if ($xmlRpcParser->_xh['isf'] > 1) { + // test that $xmlrpc->_xh['value'] is an obj, too??? + + Logger::instance()->errorLog($xmlRpcParser->_xh['isf_reason']); + + return false; + } + + switch ($xmlRpcParser->_xh['rt']) { + case 'methodresponse': + $v = $xmlRpcParser->_xh['value']; + if ($xmlRpcParser->_xh['isf'] == 1) { + /** @var Value $vc */ + $vc = $v['faultCode']; + /** @var Value $vs */ + $vs = $v['faultString']; + $r = new Response(0, $vc->scalarval(), $vs->scalarval()); + } else { + $r = new Response($v); + } + return $r; + + case 'methodcall': + $req = new Request($xmlRpcParser->_xh['method']); + for ($i = 0; $i < count($xmlRpcParser->_xh['params']); $i++) { + $req->addParam($xmlRpcParser->_xh['params'][$i]); + } + return $req; + + case 'value': + return $xmlRpcParser->_xh['value']; + + case 'fault': + // EPI api emulation + $v = $xmlRpcParser->_xh['value']; + // use a known error code + /** @var Value $vc */ + $vc = isset($v['faultCode']) ? $v['faultCode']->scalarval() : PhpXmlRpc::$xmlrpcerr['invalid_return']; + /** @var Value $vs */ + $vs = isset($v['faultString']) ? $v['faultString']->scalarval() : ''; + if (!is_int($vc) || $vc == 0) { + $vc = PhpXmlRpc::$xmlrpcerr['invalid_return']; + } + return new Response(0, $vc, $vs); + default: + return false; + } + } +} diff --git a/src/Helper/Charset.php b/src/Helper/Charset.php index 8fdecb1..eca3e46 100644 --- a/src/Helper/Charset.php +++ b/src/Helper/Charset.php @@ -103,16 +103,21 @@ class Charset /** * Convert a string to the correct XML representation in a target charset. + * This involves: + * - character transformation for all characters which have a different representation in source and dest charsets + * - using 'charset entity' representation for all characters which are outside of the target charset * * To help correct communication of non-ascii chars inside strings, regardless of the charset used when sending * requests, parsing them, sending responses and parsing responses, an option is to convert all non-ascii chars * present in the message into their equivalent 'charset entity'. Charset entities enumerated this way are * independent of the charset encoding used to transmit them, and all XML parsers are bound to understand them. - * Note that in the std case we are not sending a charset encoding mime type along with http headers, so we are - * bound by RFC 3023 to emit strict us-ascii. + * + * Note that when not sending a charset encoding mime type along with http headers, we are bound by RFC 3023 to emit + * strict us-ascii for 'text/xml' payloads (but we should review RFC 7303, which seems to have changed the rules...) * * @todo do a bit of basic benchmarking (strtr vs. str_replace) - * @todo make usage of iconv() or recode_string() or mb_string() where available + * @todo make usage of iconv() or mb_string() where available + * @todo support aliases for charset names, eg ASCII, LATIN1, ISO-88591 (see f.e. polyfill-iconv for a list) * * @param string $data * @param string $srcEncoding diff --git a/src/Helper/XMLParser.php b/src/Helper/XMLParser.php index 6233225..59eec67 100644 --- a/src/Helper/XMLParser.php +++ b/src/Helper/XMLParser.php @@ -12,6 +12,8 @@ use PhpXmlRpc\Value; * @todo implement an interface to allow for alternative implementations * - make access to $_xh protected, return more high-level data structures * - 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 */ class XMLParser { diff --git a/src/Request.php b/src/Request.php index 82f2b79..66cc788 100644 --- a/src/Request.php +++ b/src/Request.php @@ -283,6 +283,7 @@ class Request // This allows to send data which is native in various charset, by extending xmlrpc_encode_entities() and // setting xmlrpc_internalencoding if (!in_array(PhpXmlRpc::$xmlrpc_internalencoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) { + /// @todo emit a warning $options = array(XML_OPTION_TARGET_ENCODING => 'UTF-8'); } else { $options = array(XML_OPTION_TARGET_ENCODING => PhpXmlRpc::$xmlrpc_internalencoding); diff --git a/src/Server.php b/src/Server.php index d5134ba..199a7a6 100644 --- a/src/Server.php +++ b/src/Server.php @@ -524,6 +524,7 @@ class Server // This allows to send data which is native in various charset, // by extending xmlrpc_encode_entities() and setting xmlrpc_internalencoding if (!in_array(PhpXmlRpc::$xmlrpc_internalencoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) { + /// @todo emit a warning $options = array(XML_OPTION_TARGET_ENCODING => 'UTF-8'); } else { $options = array(XML_OPTION_TARGET_ENCODING => PhpXmlRpc::$xmlrpc_internalencoding); -- 2.43.0