Differences Between: [Versions 401 and 402]
1 <?php 2 3 namespace PhpXmlRpc; 4 5 use PhpXmlRpc\Helper\XMLParser; 6 use PhpXmlRpc\Traits\LoggerAware; 7 use PhpXmlRpc\Traits\ParserAware; 8 9 /** 10 * A helper class to easily convert between Value objects and php native values. 11 * 12 * @todo implement an interface 13 * @todo add class constants for the options values 14 */ 15 class Encoder 16 { 17 use LoggerAware; 18 use ParserAware; 19 20 /** 21 * Takes an xml-rpc Value in object instance and translates it into native PHP types, recursively. 22 * Works with xml-rpc Request objects as input, too. 23 * Xmlrpc dateTime values will be converted to strings or DateTime objects depending on an $options parameter 24 * Supports i8 and NIL xml-rpc values without the need for specific options. 25 * Both xml-rpc arrays and structs are decoded into PHP arrays, with the exception described below: 26 * Given proper options parameter, can rebuild generic php object instances (provided those have been encoded to 27 * xml-rpc format using a corresponding option in php_xmlrpc_encode()). 28 * PLEASE NOTE that rebuilding php objects involves calling their constructor function. 29 * This means that the remote communication end can decide which php code will get executed on your server, leaving 30 * the door possibly open to 'php-injection' style of attacks (provided you have some classes defined on your server 31 * that might wreak havoc if instances are built outside an appropriate context). 32 * Make sure you trust the remote server/client before enabling this! 33 * 34 * @author Dan Libby 35 * 36 * @param Value|Request $xmlrpcVal 37 * @param array $options accepted elements: 38 * - 'decode_php_objs': if set in the options array, xml-rpc structs can be decoded into php 39 * objects, see the details above; 40 * - 'dates_as_objects': when set xml-rpc dateTimes are decoded as php DateTime objects 41 * - 'extension_api': reserved for usage by phpxmlrpc-polyfill 42 * @return mixed 43 * 44 * Feature creep -- add an option to allow converting xml-rpc dateTime values to unix timestamps (integers) 45 */ 46 public function decode($xmlrpcVal, $options = array()) 47 { 48 switch ($xmlrpcVal->kindOf()) { 49 case 'scalar': 50 if (in_array('extension_api', $options)) { 51 $val = $xmlrpcVal->scalarVal(); 52 $typ = $xmlrpcVal->scalarTyp(); 53 switch ($typ) { 54 case 'dateTime.iso8601': 55 $xmlrpcVal = array( 56 'xmlrpc_type' => 'datetime', 57 'scalar' => $val, 58 'timestamp' => \PhpXmlRpc\Helper\Date::iso8601Decode($val) 59 ); 60 return (object)$xmlrpcVal; 61 case 'base64': 62 $xmlrpcVal = array( 63 'xmlrpc_type' => 'base64', 64 'scalar' => $val 65 ); 66 return (object)$xmlrpcVal; 67 case 'string': 68 if (isset($options['extension_api_encoding'])) { 69 // if iconv is not available, we use mb_convert_encoding 70 if (function_exists('iconv')) { 71 $dval = @iconv('UTF-8', $options['extension_api_encoding'], $val); 72 } elseif (function_exists('mb_convert_encoding')) { 73 /// @todo check for discrepancies between the supported charset names 74 $dval = @mb_convert_encoding($val, $options['extension_api_encoding'], 'UTF-8'); 75 } else { 76 $dval = false; 77 } 78 if ($dval !== false) { 79 return $dval; 80 } 81 } 82 // break through voluntarily 83 default: 84 return $val; 85 } 86 } 87 if (in_array('dates_as_objects', $options) && $xmlrpcVal->scalarTyp() == 'dateTime.iso8601') { 88 // we return a Datetime object instead of a string; since now the constructor of xml-rpc value accepts 89 // safely string, int and DateTimeInterface, we cater to all 3 cases here 90 $out = $xmlrpcVal->scalarVal(); 91 if (is_string($out)) { 92 $out = strtotime($out); 93 // NB: if the string does not convert into a timestamp, this will return false. 94 // We avoid logging an error here, as we presume it was already done when parsing the xml 95 /// @todo we could return null, to be more in line with what the XMLParser does... 96 } 97 if (is_int($out)) { 98 $result = new \DateTime(); 99 $result->setTimestamp($out); 100 101 return $result; 102 } elseif (is_a($out, 'DateTimeInterface') || is_a($out, 'DateTime')) { 103 return $out; 104 } 105 } 106 return $xmlrpcVal->scalarVal(); 107 108 case 'array': 109 $arr = array(); 110 foreach ($xmlrpcVal as $value) { 111 $arr[] = $this->decode($value, $options); 112 } 113 return $arr; 114 115 case 'struct': 116 // If user said so, try to rebuild php objects for specific struct vals. 117 /// @todo should we raise a warning for class not found? 118 // shall we check for proper subclass of xml-rpc value instead of presence of _php_class to detect 119 // what we can do? 120 if (in_array('decode_php_objs', $options) && $xmlrpcVal->_php_class != '' 121 && class_exists($xmlrpcVal->_php_class) 122 ) { 123 $obj = @new $xmlrpcVal->_php_class(); 124 foreach ($xmlrpcVal as $key => $value) { 125 $obj->$key = $this->decode($value, $options); 126 } 127 return $obj; 128 } else { 129 $arr = array(); 130 foreach ($xmlrpcVal as $key => $value) { 131 $arr[$key] = $this->decode($value, $options); 132 } 133 return $arr; 134 } 135 136 case 'msg': 137 $paramCount = $xmlrpcVal->getNumParams(); 138 $arr = array(); 139 for ($i = 0; $i < $paramCount; $i++) { 140 $arr[] = $this->decode($xmlrpcVal->getParam($i), $options); 141 } 142 return $arr; 143 144 /// @todo throw on unsupported type 145 } 146 } 147 148 /** 149 * Takes native php types and encodes them into xml-rpc Value objects, recursively. 150 * PHP strings, integers, floats and booleans have a straightforward encoding - note that integers will _not_ be 151 * converted to xml-rpc <i8> elements, even if they exceed the 32-bit range. 152 * PHP arrays will be encoded to either xml-rpc structs or arrays, depending on whether they are hashes 153 * or plain 0..N integer indexed. 154 * PHP objects will be encoded into xml-rpc structs, except if they implement DateTimeInterface, in which case they 155 * will be encoded as dateTime values. 156 * PhpXmlRpc\Value objects will not be double-encoded - which makes it possible to pass in a pre-created base64 Value 157 * as part of a php array. 158 * If given a proper $options parameter, php object instances will be encoded into 'special' xml-rpc values, that can 159 * later be decoded into php object instances by calling php_xmlrpc_decode() with a corresponding option. 160 * PHP resource and NULL variables will be converted into uninitialized Value objects (which will lead to invalid 161 * xml-rpc when later serialized); to support encoding of the latter use the appropriate $options parameter. 162 * 163 * @author Dan Libby 164 * 165 * @param mixed $phpVal the value to be converted into an xml-rpc value object 166 * @param array $options can include: 167 * - 'encode_php_objs' when set, some out-of-band info will be added to the xml produced by 168 * serializing the built Value, which can later be decoced by this library to rebuild an 169 * instance of the same php object 170 * - 'auto_dates': when set, any string which respects the xml-rpc datetime format will be converted to a dateTime Value 171 * - 'null_extension': when set, php NULL values will be converted to an xml-rpc <NIL> (or <EX:NIL>) Value 172 * - 'extension_api': reserved for usage by phpxmlrpc-polyfill 173 * @return Value 174 * 175 * Feature creep -- could support more types via optional type argument (string => datetime support has been added, 176 * ??? => base64 not yet). Also: allow auto-encoding of integers to i8 when too-big to fit into i4 177 */ 178 public function encode($phpVal, $options = array()) 179 { 180 $type = gettype($phpVal); 181 switch ($type) { 182 case 'string': 183 if (in_array('auto_dates', $options) && preg_match(PhpXmlRpc::$xmlrpc_datetime_format, $phpVal)) { 184 $xmlrpcVal = new Value($phpVal, Value::$xmlrpcDateTime); 185 } else { 186 $xmlrpcVal = new Value($phpVal, Value::$xmlrpcString); 187 } 188 break; 189 case 'integer': 190 $xmlrpcVal = new Value($phpVal, Value::$xmlrpcInt); 191 break; 192 case 'double': 193 $xmlrpcVal = new Value($phpVal, Value::$xmlrpcDouble); 194 break; 195 case 'boolean': 196 $xmlrpcVal = new Value($phpVal, Value::$xmlrpcBoolean); 197 break; 198 case 'array': 199 // A shorter one-liner would be 200 // $tmp = array_diff(array_keys($phpVal), range(0, count($phpVal)-1)); 201 // but execution time skyrockets! 202 $j = 0; 203 $arr = array(); 204 $ko = false; 205 foreach ($phpVal as $key => $val) { 206 $arr[$key] = $this->encode($val, $options); 207 if (!$ko && $key !== $j) { 208 $ko = true; 209 } 210 $j++; 211 } 212 if ($ko) { 213 $xmlrpcVal = new Value($arr, Value::$xmlrpcStruct); 214 } else { 215 $xmlrpcVal = new Value($arr, Value::$xmlrpcArray); 216 } 217 break; 218 case 'object': 219 if (is_a($phpVal, 'PhpXmlRpc\Value')) { 220 $xmlrpcVal = $phpVal; 221 // DateTimeInterface is not present in php 5.4... 222 } elseif (is_a($phpVal, 'DateTimeInterface') || is_a($phpVal, 'DateTime')) { 223 $xmlrpcVal = new Value($phpVal->format('Ymd\TH:i:s'), Value::$xmlrpcDateTime); 224 } elseif (in_array('extension_api', $options) && $phpVal instanceof \stdClass && isset($phpVal->xmlrpc_type)) { 225 // Handle the 'pre-converted' base64 and datetime values 226 if (isset($phpVal->scalar)) { 227 switch ($phpVal->xmlrpc_type) { 228 case 'base64': 229 $xmlrpcVal = new Value($phpVal->scalar, Value::$xmlrpcBase64); 230 break; 231 case 'datetime': 232 $xmlrpcVal = new Value($phpVal->scalar, Value::$xmlrpcDateTime); 233 break; 234 default: 235 $xmlrpcVal = new Value(); 236 } 237 } else { 238 $xmlrpcVal = new Value(); 239 } 240 241 } else { 242 $arr = array(); 243 foreach ($phpVal as $k => $v) { 244 $arr[$k] = $this->encode($v, $options); 245 } 246 $xmlrpcVal = new Value($arr, Value::$xmlrpcStruct); 247 if (in_array('encode_php_objs', $options)) { 248 // let's save original class name into xml-rpc value: it might be useful later on... 249 $xmlrpcVal->_php_class = get_class($phpVal); 250 } 251 } 252 break; 253 case 'NULL': 254 if (in_array('extension_api', $options)) { 255 $xmlrpcVal = new Value('', Value::$xmlrpcString); 256 } elseif (in_array('null_extension', $options)) { 257 $xmlrpcVal = new Value('', Value::$xmlrpcNull); 258 } else { 259 $xmlrpcVal = new Value(); 260 } 261 break; 262 case 'resource': 263 if (in_array('extension_api', $options)) { 264 $xmlrpcVal = new Value((int)$phpVal, Value::$xmlrpcInt); 265 } else { 266 $xmlrpcVal = new Value(); 267 } 268 break; 269 // catch "user function", "unknown type" 270 default: 271 // it has to return an empty object in case, not a boolean. (giancarlo pinerolo) 272 $xmlrpcVal = new Value(); 273 break; 274 } 275 276 return $xmlrpcVal; 277 } 278 279 /** 280 * Convert the xml representation of a method response, method request or single 281 * xml-rpc value into the appropriate object (a.k.a. deserialize). 282 * 283 * @param string $xmlVal 284 * @param array $options unused atm 285 * @return Value|Request|Response|false false on error, or an instance of either Value, Request or Response 286 * 287 * @todo is this a good name/class for this method? It does something quite different from 'decode' after all 288 * (returning objects vs returns plain php values)... In fact, it belongs rather to a Parser class 289 * @todo feature creep -- we should allow an option to return php native types instead of PhpXmlRpc objects instances 290 * @todo feature creep -- allow source charset to be passed in as an option, in case the xml misses its declaration 291 * @todo feature creep -- allow expected type (val/req/resp) to be passed in as an option 292 */ 293 public function decodeXml($xmlVal, $options = array()) 294 { 295 // 'guestimate' encoding 296 $valEncoding = XMLParser::guessEncoding('', $xmlVal); 297 if ($valEncoding != '') { 298 299 // Since parsing will fail if 300 // - charset is not specified in the xml declaration, 301 // - the encoding is not UTF8 and 302 // - there are non-ascii chars in the text, 303 // we try to work round that... 304 // The following code might be better for mb_string enabled installs, but makes the lib about 200% slower... 305 //if (!is_valid_charset($valEncoding, array('UTF-8')) 306 if (!in_array($valEncoding, array('UTF-8', 'US-ASCII')) && !XMLParser::hasEncoding($xmlVal)) { 307 if (function_exists('mb_convert_encoding')) { 308 $xmlVal = mb_convert_encoding($xmlVal, 'UTF-8', $valEncoding); 309 } else { 310 if ($valEncoding == 'ISO-8859-1') { 311 $xmlVal = utf8_encode($xmlVal); 312 } else { 313 $this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': invalid charset encoding of xml text: ' . $valEncoding); 314 } 315 } 316 } 317 } 318 319 // What if internal encoding is not in one of the 3 allowed? We use the broadest one, i.e. utf8! 320 /// @todo with php < 5.6, this does not work. We should add a manual conversion of the xml string to UTF8 321 if (in_array(PhpXmlRpc::$xmlrpc_internalencoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) { 322 $parserOptions = array(XML_OPTION_TARGET_ENCODING => PhpXmlRpc::$xmlrpc_internalencoding); 323 } else { 324 $parserOptions = array(XML_OPTION_TARGET_ENCODING => 'UTF-8', 'target_charset' => PhpXmlRpc::$xmlrpc_internalencoding); 325 } 326 327 $xmlRpcParser = $this->getParser(); 328 $_xh = $xmlRpcParser->parse( 329 $xmlVal, 330 XMLParser::RETURN_XMLRPCVALS, 331 XMLParser::ACCEPT_REQUEST | XMLParser::ACCEPT_RESPONSE | XMLParser::ACCEPT_VALUE | XMLParser::ACCEPT_FAULT, 332 $parserOptions 333 ); 334 // BC 335 if (!is_array($_xh)) { 336 $_xh = $xmlRpcParser->_xh; 337 } 338 339 if ($_xh['isf'] > 1) { 340 // test that $_xh['value'] is an obj, too??? 341 342 $this->getLogger()->error('XML-RPC: ' . $_xh['isf_reason']); 343 344 return false; 345 } 346 347 switch ($_xh['rt']) { 348 case 'methodresponse': 349 $v = $_xh['value']; 350 if ($_xh['isf'] == 1) { 351 /** @var Value $vc */ 352 $vc = $v['faultCode']; 353 /** @var Value $vs */ 354 $vs = $v['faultString']; 355 $r = new Response(0, $vc->scalarVal(), $vs->scalarVal()); 356 } else { 357 $r = new Response($v); 358 } 359 return $r; 360 361 case 'methodcall': 362 $req = new Request($_xh['method']); 363 for ($i = 0; $i < count($_xh['params']); $i++) { 364 $req->addParam($_xh['params'][$i]); 365 } 366 return $req; 367 368 case 'value': 369 return $_xh['value']; 370 371 case 'fault': 372 // EPI api emulation 373 $v = $_xh['value']; 374 // use a known error code 375 /** @var Value $vc */ 376 $vc = isset($v['faultCode']) ? $v['faultCode']->scalarVal() : PhpXmlRpc::$xmlrpcerr['invalid_return']; 377 /** @var Value $vs */ 378 $vs = isset($v['faultString']) ? $v['faultString']->scalarVal() : ''; 379 if (!is_int($vc) || $vc == 0) { 380 $vc = PhpXmlRpc::$xmlrpcerr['invalid_return']; 381 } 382 return new Response(0, $vc, $vs); 383 384 default: 385 return false; 386 } 387 } 388 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body