5 use PhpXmlRpc\Helper\XMLParser;
10 * Takes an xmlrpc value in object format and translates it into native PHP types.
12 * Works with xmlrpc requests objects as input, too.
14 * Given proper options parameter, can rebuild generic php object instances
15 * (provided those have been encoded to xmlrpc format using a corresponding
16 * option in php_xmlrpc_encode())
17 * PLEASE NOTE that rebuilding php objects involves calling their constructor function.
18 * This means that the remote communication end can decide which php code will
19 * get executed on your server, leaving the door possibly open to 'php-injection'
20 * style of attacks (provided you have some classes defined on your server that
21 * might wreak havoc if instances are built outside an appropriate context).
22 * Make sure you trust the remote server/client before eanbling this!
24 * @author Dan Libby (dan@libby.com)
26 * @param Value|Request $xmlrpc_val
27 * @param array $options if 'decode_php_objs' is set in the options array, xmlrpc structs can be decoded into php objects; if 'dates_as_objects' is set xmlrpc datetimes are decoded as php DateTime objects (standard is
31 public function decode($xmlrpc_val, $options = array())
33 switch ($xmlrpc_val->kindOf()) {
35 if (in_array('extension_api', $options)) {
36 reset($xmlrpc_val->me);
37 list($typ, $val) = each($xmlrpc_val->me);
39 case 'dateTime.iso8601':
40 $xmlrpc_val->scalar = $val;
41 $xmlrpc_val->type = 'datetime';
42 $xmlrpc_val->timestamp = \PhpXmlRpc\Helper\Date::iso8601_decode($val);
46 $xmlrpc_val->scalar = $val;
47 $xmlrpc_val->type = $typ;
51 return $xmlrpc_val->scalarval();
54 if (in_array('dates_as_objects', $options) && $xmlrpc_val->scalartyp() == 'dateTime.iso8601') {
55 // we return a Datetime object instead of a string
56 // since now the constructor of xmlrpc value accepts safely strings, ints and datetimes,
57 // we cater to all 3 cases here
58 $out = $xmlrpc_val->scalarval();
59 if (is_string($out)) {
60 $out = strtotime($out);
63 $result = new \Datetime();
64 $result->setTimestamp($out);
67 } elseif (is_a($out, 'Datetime')) {
72 return $xmlrpc_val->scalarval();
74 $size = $xmlrpc_val->arraysize();
76 for ($i = 0; $i < $size; $i++) {
77 $arr[] = $this->decode($xmlrpc_val->arraymem($i), $options);
82 $xmlrpc_val->structreset();
83 // If user said so, try to rebuild php objects for specific struct vals.
84 /// @todo should we raise a warning for class not found?
85 // shall we check for proper subclass of xmlrpc value instead of
86 // presence of _php_class to detect what we can do?
87 if (in_array('decode_php_objs', $options) && $xmlrpc_val->_php_class != ''
88 && class_exists($xmlrpc_val->_php_class)
90 $obj = @new $xmlrpc_val->_php_class();
91 while (list($key, $value) = $xmlrpc_val->structeach()) {
92 $obj->$key = $this->decode($value, $options);
98 while (list($key, $value) = $xmlrpc_val->structeach()) {
99 $arr[$key] = $this->decode($value, $options);
105 $paramcount = $xmlrpc_val->getNumParams();
107 for ($i = 0; $i < $paramcount; $i++) {
108 $arr[] = $this->decode($xmlrpc_val->getParam($i));
116 * Takes native php types and encodes them into xmlrpc PHP object format.
117 * It will not re-encode xmlrpc value objects.
119 * Feature creep -- could support more types via optional type argument
120 * (string => datetime support has been added, ??? => base64 not yet)
122 * If given a proper options parameter, php object instances will be encoded
123 * into 'special' xmlrpc values, that can later be decoded into php objects
124 * by calling php_xmlrpc_decode() with a corresponding option
126 * @author Dan Libby (dan@libby.com)
128 * @param mixed $php_val the value to be converted into an xmlrpc value object
129 * @param array $options can include 'encode_php_objs', 'auto_dates', 'null_extension' or 'extension_api'
131 * @return \PhpXmlrpc\Value
133 public function encode($php_val, $options = array())
135 $type = gettype($php_val);
138 if (in_array('auto_dates', $options) && preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $php_val)) {
139 $xmlrpc_val = new Value($php_val, Value::$xmlrpcDateTime);
141 $xmlrpc_val = new Value($php_val, Value::$xmlrpcString);
145 $xmlrpc_val = new Value($php_val, Value::$xmlrpcInt);
148 $xmlrpc_val = new Value($php_val, Value::$xmlrpcDouble);
150 // <G_Giunta_2001-02-29>
151 // Add support for encoding/decoding of booleans, since they are supported in PHP
153 $xmlrpc_val = new Value($php_val, Value::$xmlrpcBoolean);
155 // </G_Giunta_2001-02-29>
157 // PHP arrays can be encoded to either xmlrpc structs or arrays,
158 // depending on wheter they are hashes or plain 0..n integer indexed
159 // A shorter one-liner would be
160 // $tmp = array_diff(array_keys($php_val), range(0, count($php_val)-1));
161 // but execution time skyrockets!
165 foreach ($php_val as $key => $val) {
166 $arr[$key] = $this->encode($val, $options);
167 if (!$ko && $key !== $j) {
173 $xmlrpc_val = new Value($arr, Value::$xmlrpcStruct);
175 $xmlrpc_val = new Value($arr, Value::$xmlrpcArray);
179 if (is_a($php_val, 'PhpXmlRpc\Value')) {
180 $xmlrpc_val = $php_val;
181 } elseif (is_a($php_val, 'DateTime')) {
182 $xmlrpc_val = new Value($php_val->format('Ymd\TH:i:s'), Value::$xmlrpcStruct);
186 while (list($k, $v) = each($php_val)) {
187 $arr[$k] = $this->encode($v, $options);
189 $xmlrpc_val = new Value($arr, Value::$xmlrpcStruct);
190 if (in_array('encode_php_objs', $options)) {
191 // let's save original class name into xmlrpc value:
192 // might be useful later on...
193 $xmlrpc_val->_php_class = get_class($php_val);
198 if (in_array('extension_api', $options)) {
199 $xmlrpc_val = new Value('', Value::$xmlrpcString);
200 } elseif (in_array('null_extension', $options)) {
201 $xmlrpc_val = new Value('', Value::$xmlrpcNull);
203 $xmlrpc_val = new Value();
207 if (in_array('extension_api', $options)) {
208 $xmlrpc_val = new Value((int)$php_val, Value::$xmlrpcInt);
210 $xmlrpc_val = new Value();
212 // catch "user function", "unknown type"
214 // giancarlo pinerolo <ping@alt.it>
216 // an empty object in case, not a boolean.
217 $xmlrpc_val = new Value();
225 * Convert the xml representation of a method response, method request or single
226 * xmlrpc value into the appropriate object (a.k.a. deserialize).
228 * @param string $xml_val
229 * @param array $options
231 * @return mixed false on error, or an instance of either Value, Request or Response
233 public function decode_xml($xml_val, $options = array())
236 /// @todo 'guestimate' encoding
237 $parser = xml_parser_create();
238 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
239 // What if internal encoding is not in one of the 3 allowed?
240 // we use the broadest one, ie. utf8!
241 if (!in_array(PhpXmlRpc::$xmlrpc_internalencoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) {
242 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
244 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, PhpXmlRpc::$xmlrpc_internalencoding);
247 $xmlRpcParser = new XMLParser();
248 xml_set_object($parser, $xmlRpcParser);
250 xml_set_element_handler($parser, 'xmlrpc_se_any', 'xmlrpc_ee');
251 xml_set_character_data_handler($parser, 'xmlrpc_cd');
252 xml_set_default_handler($parser, 'xmlrpc_dh');
253 if (!xml_parse($parser, $xml_val, 1)) {
254 $errstr = sprintf('XML error: %s at line %d, column %d',
255 xml_error_string(xml_get_error_code($parser)),
256 xml_get_current_line_number($parser), xml_get_current_column_number($parser));
258 xml_parser_free($parser);
262 xml_parser_free($parser);
263 if ($xmlRpcParser->_xh['isf'] > 1) {
264 // test that $xmlrpc->_xh['value'] is an obj, too???
266 error_log($xmlRpcParser->_xh['isf_reason']);
270 switch ($xmlRpcParser->_xh['rt']) {
271 case 'methodresponse':
272 $v = &$xmlRpcParser->_xh['value'];
273 if ($xmlRpcParser->_xh['isf'] == 1) {
274 $vc = $v->structmem('faultCode');
275 $vs = $v->structmem('faultString');
276 $r = new Response(0, $vc->scalarval(), $vs->scalarval());
278 $r = new Response($v);
283 $m = new Request($xmlRpcParser->_xh['method']);
284 for ($i = 0; $i < count($xmlRpcParser->_xh['params']); $i++) {
285 $m->addParam($xmlRpcParser->_xh['params'][$i]);
290 return $xmlRpcParser->_xh['value'];
297 * xml charset encoding guessing helper function.
298 * Tries to determine the charset encoding of an XML chunk received over HTTP.
299 * NB: according to the spec (RFC 3023), if text/xml content-type is received over HTTP without a content-type,
300 * we SHOULD assume it is strictly US-ASCII. But we try to be more tolerant of unconforming (legacy?) clients/servers,
301 * which will be most probably using UTF-8 anyway...
303 * @param string $httpheader the http Content-type header
304 * @param string $xmlchunk xml content buffer
305 * @param string $encoding_prefs comma separated list of character encodings to be used as default (when mb extension is enabled)
308 * @todo explore usage of mb_http_input(): does it detect http headers + post data? if so, use it instead of hand-detection!!!
310 public static function guess_encoding($httpheader = '', $xmlchunk = '', $encoding_prefs = null)
312 // discussion: see http://www.yale.edu/pclt/encoding/
313 // 1 - test if encoding is specified in HTTP HEADERS
316 // LWS: (\13\10)?( |\t)+
317 // token: (any char but excluded stuff)+
318 // quoted string: " (any char but double quotes and cointrol chars)* "
319 // header: Content-type = ...; charset=value(; ...)*
320 // where value is of type token, no LWS allowed between 'charset' and value
321 // Note: we do not check for invalid chars in VALUE:
322 // this had better be done using pure ereg as below
323 // Note 2: we might be removing whitespace/tabs that ought to be left in if
324 // the received charset is a quoted string. But nobody uses such charset names...
326 /// @todo this test will pass if ANY header has charset specification, not only Content-Type. Fix it?
328 if (preg_match('/;\s*charset\s*=([^;]+)/i', $httpheader, $matches)) {
329 return strtoupper(trim($matches[1], " \t\""));
332 // 2 - scan the first bytes of the data for a UTF-16 (or other) BOM pattern
333 // (source: http://www.w3.org/TR/2000/REC-xml-20001006)
334 // NOTE: actually, according to the spec, even if we find the BOM and determine
335 // an encoding, we should check if there is an encoding specified
336 // in the xml declaration, and verify if they match.
337 /// @todo implement check as described above?
338 /// @todo implement check for first bytes of string even without a BOM? (It sure looks harder than for cases WITH a BOM)
339 if (preg_match('/^(\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\x00\x00\xFF\xFE|\xFE\xFF\x00\x00)/', $xmlchunk)) {
341 } elseif (preg_match('/^(\xFE\xFF|\xFF\xFE)/', $xmlchunk)) {
343 } elseif (preg_match('/^(\xEF\xBB\xBF)/', $xmlchunk)) {
347 // 3 - test if encoding is specified in the xml declaration
349 // SPACE: (#x20 | #x9 | #xD | #xA)+ === [ \x9\xD\xA]+
350 // EQ: SPACE?=SPACE? === [ \x9\xD\xA]*=[ \x9\xD\xA]*
351 if (preg_match('/^<\?xml\s+version\s*=\s*' . "((?:\"[a-zA-Z0-9_.:-]+\")|(?:'[a-zA-Z0-9_.:-]+'))" .
352 '\s+encoding\s*=\s*' . "((?:\"[A-Za-z][A-Za-z0-9._-]*\")|(?:'[A-Za-z][A-Za-z0-9._-]*'))/",
353 $xmlchunk, $matches)) {
354 return strtoupper(substr($matches[2], 1, -1));
357 // 4 - if mbstring is available, let it do the guesswork
358 // NB: we favour finding an encoding that is compatible with what we can process
359 if (extension_loaded('mbstring')) {
360 if ($encoding_prefs) {
361 $enc = mb_detect_encoding($xmlchunk, $encoding_prefs);
363 $enc = mb_detect_encoding($xmlchunk);
365 // NB: mb_detect likes to call it ascii, xml parser likes to call it US_ASCII...
366 // IANA also likes better US-ASCII, so go with it
367 if ($enc == 'ASCII') {
373 // no encoding specified: as per HTTP1.1 assume it is iso-8859-1?
374 // Both RFC 2616 (HTTP 1.1) and 1945 (HTTP 1.0) clearly state that for text/xxx content types
375 // this should be the standard. And we should be getting text/xml as request and response.
376 // BUT we have to be backward compatible with the lib, which always used UTF-8 as default...
377 return PhpXmlRpc::$xmlrpc_defencoding;