5 use PhpXmlRpc\Helper\XMLParser;
10 * Takes an xmlrpc value in PHP xmlrpcval 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
30 function decode($xmlrpc_val, $options=array())
32 switch($xmlrpc_val->kindOf())
35 if (in_array('extension_api', $options))
37 reset($xmlrpc_val->me);
38 list($typ,$val) = each($xmlrpc_val->me);
41 case 'dateTime.iso8601':
42 $xmlrpc_val->scalar = $val;
43 $xmlrpc_val->type = 'datetime';
44 $xmlrpc_val->timestamp = \PhpXmlRpc\Helper\Date::iso8601_decode($val);
47 $xmlrpc_val->scalar = $val;
48 $xmlrpc_val->type = $typ;
51 return $xmlrpc_val->scalarval();
54 if (in_array('dates_as_objects', $options) && $xmlrpc_val->scalartyp() == 'dateTime.iso8601')
56 // we return a Datetime object instead of a string
57 // since now the constructor of xmlrpcval accepts safely strings, ints and datetimes,
58 // we cater to all 3 cases here
59 $out = $xmlrpc_val->scalarval();
62 $out = strtotime($out);
66 $result = new \Datetime();
67 $result->setTimestamp($out);
70 elseif (is_a($out, 'Datetime'))
75 return $xmlrpc_val->scalarval();
77 $size = $xmlrpc_val->arraysize();
79 for($i = 0; $i < $size; $i++)
81 $arr[] = $this->decode($xmlrpc_val->arraymem($i), $options);
85 $xmlrpc_val->structreset();
86 // If user said so, try to rebuild php objects for specific struct vals.
87 /// @todo should we raise a warning for class not found?
88 // shall we check for proper subclass of xmlrpcval instead of
89 // presence of _php_class to detect what we can do?
90 if (in_array('decode_php_objs', $options) && $xmlrpc_val->_php_class != ''
91 && class_exists($xmlrpc_val->_php_class))
93 $obj = @new $xmlrpc_val->_php_class;
94 while(list($key,$value)=$xmlrpc_val->structeach())
96 $obj->$key = $this->decode($value, $options);
103 while(list($key,$value)=$xmlrpc_val->structeach())
105 $arr[$key] = $this->decode($value, $options);
110 $paramcount = $xmlrpc_val->getNumParams();
112 for($i = 0; $i < $paramcount; $i++)
114 $arr[] = $this->decode($xmlrpc_val->getParam($i));
121 * Takes native php types and encodes them into xmlrpc PHP object format.
122 * It will not re-encode xmlrpcval objects.
124 * Feature creep -- could support more types via optional type argument
125 * (string => datetime support has been added, ??? => base64 not yet)
127 * If given a proper options parameter, php object instances will be encoded
128 * into 'special' xmlrpc values, that can later be decoded into php objects
129 * by calling php_xmlrpc_decode() with a corresponding option
131 * @author Dan Libby (dan@libby.com)
133 * @param mixed $php_val the value to be converted into an xmlrpcval object
134 * @param array $options can include 'encode_php_objs', 'auto_dates', 'null_extension' or 'extension_api'
135 * @return \PhpXmlrpc\Value
137 function encode($php_val, $options=array())
139 $type = gettype($php_val);
143 if (in_array('auto_dates', $options) && preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $php_val))
144 $xmlrpc_val = new Value($php_val, Value::$xmlrpcDateTime);
146 $xmlrpc_val = new Value($php_val, Value::$xmlrpcString);
149 $xmlrpc_val = new Value($php_val, Value::$xmlrpcInt);
152 $xmlrpc_val = new Value($php_val, Value::$xmlrpcDouble);
154 // <G_Giunta_2001-02-29>
155 // Add support for encoding/decoding of booleans, since they are supported in PHP
157 $xmlrpc_val = new Value($php_val, Value::$xmlrpcBoolean);
159 // </G_Giunta_2001-02-29>
161 // PHP arrays can be encoded to either xmlrpc structs or arrays,
162 // depending on wheter they are hashes or plain 0..n integer indexed
163 // A shorter one-liner would be
164 // $tmp = array_diff(array_keys($php_val), range(0, count($php_val)-1));
165 // but execution time skyrockets!
169 foreach($php_val as $key => $val)
171 $arr[$key] = $this->encode($val, $options);
172 if(!$ko && $key !== $j)
180 $xmlrpc_val = new Value($arr, Value::$xmlrpcStruct);
184 $xmlrpc_val = new Value($arr, Value::$xmlrpcArray);
188 if(is_a($php_val, 'PhpXmlRpc\Value'))
190 $xmlrpc_val = $php_val;
192 else if(is_a($php_val, 'DateTime'))
194 $xmlrpc_val = new Value($php_val->format('Ymd\TH:i:s'), Value::$xmlrpcStruct);
200 while(list($k,$v) = each($php_val))
202 $arr[$k] = $this->encode($v, $options);
204 $xmlrpc_val = new Value($arr, Value::$xmlrpcStruct);
205 if (in_array('encode_php_objs', $options))
207 // let's save original class name into xmlrpcval:
208 // might be useful later on...
209 $xmlrpc_val->_php_class = get_class($php_val);
214 if (in_array('extension_api', $options))
216 $xmlrpc_val = new Value('', Value::$xmlrpcString);
218 else if (in_array('null_extension', $options))
220 $xmlrpc_val = new Value('', Value::$xmlrpcNull);
224 $xmlrpc_val = new Value();
228 if (in_array('extension_api', $options))
230 $xmlrpc_val = new Value((int)$php_val, Value::$xmlrpcInt);
234 $xmlrpc_val = new Value();
236 // catch "user function", "unknown type"
238 // giancarlo pinerolo <ping@alt.it>
240 // an empty object in case, not a boolean.
241 $xmlrpc_val = new Value();
248 * Convert the xml representation of a method response, method request or single
249 * xmlrpc value into the appropriate object (a.k.a. deserialize)
250 * @param string $xml_val
251 * @param array $options
252 * @return mixed false on error, or an instance of either Value, Request or Response
254 function decode_xml($xml_val, $options=array())
257 /// @todo 'guestimate' encoding
258 $parser = xml_parser_create();
259 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
260 // What if internal encoding is not in one of the 3 allowed?
261 // we use the broadest one, ie. utf8!
262 if (!in_array(PhpXmlRpc::$xmlrpc_internalencoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
264 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
268 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, PhpXmlRpc::$xmlrpc_internalencoding);
271 $xmlRpcParser = new XMLParser();
272 xml_set_object($parser, $xmlRpcParser);
274 xml_set_element_handler($parser, 'xmlrpc_se_any', 'xmlrpc_ee');
275 xml_set_character_data_handler($parser, 'xmlrpc_cd');
276 xml_set_default_handler($parser, 'xmlrpc_dh');
277 if(!xml_parse($parser, $xml_val, 1))
279 $errstr = sprintf('XML error: %s at line %d, column %d',
280 xml_error_string(xml_get_error_code($parser)),
281 xml_get_current_line_number($parser), xml_get_current_column_number($parser));
283 xml_parser_free($parser);
286 xml_parser_free($parser);
287 if ($xmlRpcParser->_xh['isf'] > 1) // test that $xmlrpc->_xh['value'] is an obj, too???
289 error_log($xmlRpcParser->_xh['isf_reason']);
292 switch ($xmlRpcParser->_xh['rt'])
294 case 'methodresponse':
295 $v =& $xmlRpcParser->_xh['value'];
296 if ($xmlRpcParser->_xh['isf'] == 1)
298 $vc = $v->structmem('faultCode');
299 $vs = $v->structmem('faultString');
300 $r = new Response(0, $vc->scalarval(), $vs->scalarval());
304 $r = new Response($v);
308 $m = new Request($xmlRpcParser->_xh['method']);
309 for($i=0; $i < count($xmlRpcParser->_xh['params']); $i++)
311 $m->addParam($xmlRpcParser->_xh['params'][$i]);
315 return $xmlRpcParser->_xh['value'];
323 * xml charset encoding guessing helper function.
324 * Tries to determine the charset encoding of an XML chunk received over HTTP.
325 * NB: according to the spec (RFC 3023), if text/xml content-type is received over HTTP without a content-type,
326 * we SHOULD assume it is strictly US-ASCII. But we try to be more tolerant of unconforming (legacy?) clients/servers,
327 * which will be most probably using UTF-8 anyway...
329 * @param string $httpheader the http Content-type header
330 * @param string $xmlchunk xml content buffer
331 * @param string $encoding_prefs comma separated list of character encodings to be used as default (when mb extension is enabled)
334 * @todo explore usage of mb_http_input(): does it detect http headers + post data? if so, use it instead of hand-detection!!!
336 static function guess_encoding($httpheader='', $xmlchunk='', $encoding_prefs=null)
338 // discussion: see http://www.yale.edu/pclt/encoding/
339 // 1 - test if encoding is specified in HTTP HEADERS
342 // LWS: (\13\10)?( |\t)+
343 // token: (any char but excluded stuff)+
344 // quoted string: " (any char but double quotes and cointrol chars)* "
345 // header: Content-type = ...; charset=value(; ...)*
346 // where value is of type token, no LWS allowed between 'charset' and value
347 // Note: we do not check for invalid chars in VALUE:
348 // this had better be done using pure ereg as below
349 // Note 2: we might be removing whitespace/tabs that ought to be left in if
350 // the received charset is a quoted string. But nobody uses such charset names...
352 /// @todo this test will pass if ANY header has charset specification, not only Content-Type. Fix it?
354 if(preg_match('/;\s*charset\s*=([^;]+)/i', $httpheader, $matches))
356 return strtoupper(trim($matches[1], " \t\""));
359 // 2 - scan the first bytes of the data for a UTF-16 (or other) BOM pattern
360 // (source: http://www.w3.org/TR/2000/REC-xml-20001006)
361 // NOTE: actually, according to the spec, even if we find the BOM and determine
362 // an encoding, we should check if there is an encoding specified
363 // in the xml declaration, and verify if they match.
364 /// @todo implement check as described above?
365 /// @todo implement check for first bytes of string even without a BOM? (It sure looks harder than for cases WITH a BOM)
366 if(preg_match('/^(\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\x00\x00\xFF\xFE|\xFE\xFF\x00\x00)/', $xmlchunk))
370 elseif(preg_match('/^(\xFE\xFF|\xFF\xFE)/', $xmlchunk))
374 elseif(preg_match('/^(\xEF\xBB\xBF)/', $xmlchunk))
379 // 3 - test if encoding is specified in the xml declaration
381 // SPACE: (#x20 | #x9 | #xD | #xA)+ === [ \x9\xD\xA]+
382 // EQ: SPACE?=SPACE? === [ \x9\xD\xA]*=[ \x9\xD\xA]*
383 if (preg_match('/^<\?xml\s+version\s*=\s*'. "((?:\"[a-zA-Z0-9_.:-]+\")|(?:'[a-zA-Z0-9_.:-]+'))".
384 '\s+encoding\s*=\s*' . "((?:\"[A-Za-z][A-Za-z0-9._-]*\")|(?:'[A-Za-z][A-Za-z0-9._-]*'))/",
385 $xmlchunk, $matches))
387 return strtoupper(substr($matches[2], 1, -1));
390 // 4 - if mbstring is available, let it do the guesswork
391 // NB: we favour finding an encoding that is compatible with what we can process
392 if(extension_loaded('mbstring'))
396 $enc = mb_detect_encoding($xmlchunk, $encoding_prefs);
400 $enc = mb_detect_encoding($xmlchunk);
402 // NB: mb_detect likes to call it ascii, xml parser likes to call it US_ASCII...
403 // IANA also likes better US-ASCII, so go with it
412 // no encoding specified: as per HTTP1.1 assume it is iso-8859-1?
413 // Both RFC 2616 (HTTP 1.1) and 1945 (HTTP 1.0) clearly state that for text/xxx content types
414 // this should be the standard. And we should be getting text/xml as request and response.
415 // BUT we have to be backward compatible with the lib, which always used UTF-8 as default...
416 return PhpXmlRpc::$xmlrpc_defencoding;