Renamed Xmlrpc class to Phpxmlrpc
[plcapi.git] / lib / xmlrpc.php
1 <?php\r
2 // by Edd Dumbill (C) 1999-2002\r
3 // <edd@usefulinc.com>\r
4 \r
5 // Copyright (c) 1999,2000,2002 Edd Dumbill.\r
6 // All rights reserved.\r
7 //\r
8 // Redistribution and use in source and binary forms, with or without\r
9 // modification, are permitted provided that the following conditions\r
10 // are met:\r
11 //\r
12 //    * Redistributions of source code must retain the above copyright\r
13 //      notice, this list of conditions and the following disclaimer.\r
14 //\r
15 //    * Redistributions in binary form must reproduce the above\r
16 //      copyright notice, this list of conditions and the following\r
17 //      disclaimer in the documentation and/or other materials provided\r
18 //      with the distribution.\r
19 //\r
20 //    * Neither the name of the "XML-RPC for PHP" nor the names of its\r
21 //      contributors may be used to endorse or promote products derived\r
22 //      from this software without specific prior written permission.\r
23 //\r
24 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\r
25 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\r
26 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS\r
27 // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE\r
28 // REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\r
29 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\r
30 // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\r
31 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\r
32 // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\r
33 // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r
34 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\r
35 // OF THE POSSIBILITY OF SUCH DAMAGE.\r
36 \r
37 require_once __DIR__ . "/xmlrpc_client.php";\r
38 require_once __DIR__ . "/xmlrpcresp.php";\r
39 require_once __DIR__ . "/xmlrpcmsg.php";\r
40 require_once __DIR__ . "/xmlrpcval.php";\r
41 \r
42 class Phpxmlrpc {\r
43 \r
44     public $xmlrpcI4 = "i4";\r
45     public $xmlrpcInt = "int";\r
46     public $xmlrpcBoolean = "boolean";\r
47     public $xmlrpcDouble = "double";\r
48     public $xmlrpcString = "string";\r
49     public $xmlrpcDateTime = "dateTime.iso8601";\r
50     public $xmlrpcBase64 = "base64";\r
51     public $xmlrpcArray = "array";\r
52     public $xmlrpcStruct = "struct";\r
53     public $xmlrpcValue = "undefined";\r
54     public $xmlrpcNull = "null";\r
55 \r
56     public $xmlrpcTypes;\r
57 \r
58     public $xmlrpc_valid_parents = array(\r
59         'VALUE' => array('MEMBER', 'DATA', 'PARAM', 'FAULT'),\r
60         'BOOLEAN' => array('VALUE'),\r
61         'I4' => array('VALUE'),\r
62         'INT' => array('VALUE'),\r
63         'STRING' => array('VALUE'),\r
64         'DOUBLE' => array('VALUE'),\r
65         'DATETIME.ISO8601' => array('VALUE'),\r
66         'BASE64' => array('VALUE'),\r
67         'MEMBER' => array('STRUCT'),\r
68         'NAME' => array('MEMBER'),\r
69         'DATA' => array('ARRAY'),\r
70         'ARRAY' => array('VALUE'),\r
71         'STRUCT' => array('VALUE'),\r
72         'PARAM' => array('PARAMS'),\r
73         'METHODNAME' => array('METHODCALL'),\r
74         'PARAMS' => array('METHODCALL', 'METHODRESPONSE'),\r
75         'FAULT' => array('METHODRESPONSE'),\r
76         'NIL' => array('VALUE'), // only used when extension activated\r
77         'EX:NIL' => array('VALUE') // only used when extension activated\r
78     );\r
79 \r
80     // tables used for transcoding different charsets into us-ascii xml\r
81     public $xml_iso88591_Entities = array("in" => array(), "out" => array());\r
82 \r
83     /// @todo add to iso table the characters from cp_1252 range, i.e. 128 to 159?\r
84     /// These will NOT be present in true ISO-8859-1, but will save the unwary\r
85     /// windows user from sending junk (though no luck when reciving them...)\r
86     /*\r
87     $GLOBALS['xml_cp1252_Entities']=array();\r
88     for ($i = 128; $i < 160; $i++)\r
89     {\r
90         $GLOBALS['xml_cp1252_Entities']['in'][] = chr($i);\r
91     }\r
92     $GLOBALS['xml_cp1252_Entities']['out'] = array(\r
93         '&#x20AC;', '?',        '&#x201A;', '&#x0192;',\r
94         '&#x201E;', '&#x2026;', '&#x2020;', '&#x2021;',\r
95         '&#x02C6;', '&#x2030;', '&#x0160;', '&#x2039;',\r
96         '&#x0152;', '?',        '&#x017D;', '?',\r
97         '?',        '&#x2018;', '&#x2019;', '&#x201C;',\r
98         '&#x201D;', '&#x2022;', '&#x2013;', '&#x2014;',\r
99         '&#x02DC;', '&#x2122;', '&#x0161;', '&#x203A;',\r
100         '&#x0153;', '?',        '&#x017E;', '&#x0178;'\r
101     );\r
102     */\r
103 \r
104     public $xmlrpcerr = array(\r
105         'unknown_method'=>1,\r
106         'invalid_return'=>2,\r
107         'incorrect_params'=>3,\r
108         'introspect_unknown'=>4,\r
109         'http_error'=>5,\r
110         'no_data'=>6,\r
111         'no_ssl'=>7,\r
112         'curl_fail'=>8,\r
113         'invalid_request'=>15,\r
114         'no_curl'=>16,\r
115         'server_error'=>17,\r
116         'multicall_error'=>18,\r
117         'multicall_notstruct'=>9,\r
118         'multicall_nomethod'=>10,\r
119         'multicall_notstring'=>11,\r
120         'multicall_recursion'=>12,\r
121         'multicall_noparams'=>13,\r
122         'multicall_notarray'=>14,\r
123 \r
124         'cannot_decompress'=>103,\r
125         'decompress_fail'=>104,\r
126         'dechunk_fail'=>105,\r
127         'server_cannot_decompress'=>106,\r
128         'server_decompress_fail'=>107\r
129     );\r
130 \r
131     public $xmlrpcstr = array(\r
132         'unknown_method'=>'Unknown method',\r
133         'invalid_return'=>'Invalid return payload: enable debugging to examine incoming payload',\r
134         'incorrect_params'=>'Incorrect parameters passed to method',\r
135         'introspect_unknown'=>"Can't introspect: method unknown",\r
136         'http_error'=>"Didn't receive 200 OK from remote server.",\r
137         'no_data'=>'No data received from server.',\r
138         'no_ssl'=>'No SSL support compiled in.',\r
139         'curl_fail'=>'CURL error',\r
140         'invalid_request'=>'Invalid request payload',\r
141         'no_curl'=>'No CURL support compiled in.',\r
142         'server_error'=>'Internal server error',\r
143         'multicall_error'=>'Received from server invalid multicall response',\r
144         'multicall_notstruct'=>'system.multicall expected struct',\r
145         'multicall_nomethod'=>'missing methodName',\r
146         'multicall_notstring'=>'methodName is not a string',\r
147         'multicall_recursion'=>'recursive system.multicall forbidden',\r
148         'multicall_noparams'=>'missing params',\r
149         'multicall_notarray'=>'params is not an array',\r
150 \r
151         'cannot_decompress'=>'Received from server compressed HTTP and cannot decompress',\r
152         'decompress_fail'=>'Received from server invalid compressed HTTP',\r
153         'dechunk_fail'=>'Received from server invalid chunked HTTP',\r
154         'server_cannot_decompress'=>'Received from client compressed HTTP request and cannot decompress',\r
155         'server_decompress_fail'=>'Received from client invalid compressed HTTP request'\r
156     );\r
157 \r
158     // The charset encoding used by the server for received messages and\r
159     // by the client for received responses when received charset cannot be determined\r
160     // or is not supported\r
161     public $xmlrpc_defencoding = "UTF-8";\r
162 \r
163     // The encoding used internally by PHP.\r
164     // String values received as xml will be converted to this, and php strings will be converted to xml\r
165     // as if having been coded with this\r
166     public $xmlrpc_internalencoding = "ISO-8859-1"; // TODO: maybe this would be better as UTF-8, or atleast configurable?\r
167 \r
168     public $xmlrpcName = "XML-RPC for PHP";\r
169     public $xmlrpcVersion = "3.0.0.beta";\r
170 \r
171     // let user errors start at 800\r
172     public $xmlrpcerruser = 800;\r
173     // let XML parse errors start at 100\r
174     public $xmlrpcerrxml = 100;\r
175 \r
176     // set to TRUE to enable correct decoding of <NIL/> and <EX:NIL/> values\r
177     public $xmlrpc_null_extension = false;\r
178 \r
179     // set to TRUE to enable encoding of php NULL values to <EX:NIL/> instead of <NIL/>\r
180     public $xmlrpc_null_apache_encoding = false;\r
181 \r
182     public $xmlrpc_null_apache_encoding_ns = "http://ws.apache.org/xmlrpc/namespaces/extensions";\r
183 \r
184     // used to store state during parsing\r
185     // quick explanation of components:\r
186     //   ac - used to accumulate values\r
187     //   isf - used to indicate a parsing fault (2) or xmlrpcresp fault (1)\r
188     //   isf_reason - used for storing xmlrpcresp fault string\r
189     //   lv - used to indicate "looking for a value": implements\r
190     //        the logic to allow values with no types to be strings\r
191     //   params - used to store parameters in method calls\r
192     //   method - used to store method name\r
193     //   stack - array with genealogy of xml elements names:\r
194     //           used to validate nesting of xmlrpc elements\r
195     public $_xh = null;\r
196 \r
197     private static $instance = null;\r
198 \r
199     private function __construct() {\r
200         $this->xmlrpcTypes = array(\r
201             $this->xmlrpcI4 => 1,\r
202             $this->xmlrpcInt => 1,\r
203             $this->xmlrpcBoolean => 1,\r
204             $this->xmlrpcDouble => 1,\r
205             $this->xmlrpcString => 1,\r
206             $this->xmlrpcDateTime => 1,\r
207             $this->xmlrpcBase64 => 1,\r
208             $this->xmlrpcArray => 2,\r
209             $this->xmlrpcStruct => 3,\r
210             $this->xmlrpcNull => 1\r
211         );\r
212 \r
213         for($i = 0; $i < 32; $i++) {\r
214             $this->xml_iso88591_Entities["in"][] = chr($i);\r
215             $this->xml_iso88591_Entities["out"][] = "&#{$i};";\r
216         }\r
217 \r
218         for($i = 160; $i < 256; $i++) {\r
219             $this->xml_iso88591_Entities["in"][] = chr($i);\r
220             $this->xml_iso88591_Entities["out"][] = "&#{$i};";\r
221         }\r
222     }\r
223 \r
224     /**\r
225     * This class is singleton for performance reasons: this way the ASCII array needs to be done only once.\r
226     */\r
227     public static function instance() {\r
228         if(Phpxmlrpc::$instance === null) {\r
229             Phpxmlrpc::$instance = new Xmlrpc();\r
230         }\r
231 \r
232         return Phpxmlrpc::$instance;\r
233     }\r
234 }\r
235 \r
236 \r
237 /**\r
238  * Convert a string to the correct XML representation in a target charset\r
239  * To help correct communication of non-ascii chars inside strings, regardless\r
240  * of the charset used when sending requests, parsing them, sending responses\r
241  * and parsing responses, an option is to convert all non-ascii chars present in the message\r
242  * into their equivalent 'charset entity'. Charset entities enumerated this way\r
243  * are independent of the charset encoding used to transmit them, and all XML\r
244  * parsers are bound to understand them.\r
245  * Note that in the std case we are not sending a charset encoding mime type\r
246  * along with http headers, so we are bound by RFC 3023 to emit strict us-ascii.\r
247  *\r
248  * @todo do a bit of basic benchmarking (strtr vs. str_replace)\r
249  * @todo        make usage of iconv() or recode_string() or mb_string() where available\r
250  */\r
251 function xmlrpc_encode_entitites($data, $src_encoding='', $dest_encoding='')\r
252 {\r
253     $xmlrpc = Phpxmlrpc::instance();\r
254     if ($src_encoding == '')\r
255     {\r
256         // lame, but we know no better...\r
257         $src_encoding = $xmlrpc->xmlrpc_internalencoding;\r
258     }\r
259 \r
260     switch(strtoupper($src_encoding.'_'.$dest_encoding))\r
261     {\r
262         case 'ISO-8859-1_':\r
263         case 'ISO-8859-1_US-ASCII':\r
264             $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);\r
265             $escaped_data = str_replace($xmlrpc->xml_iso88591_Entities['in'], $xmlrpc->xml_iso88591_Entities['out'], $escaped_data);\r
266             break;\r
267         case 'ISO-8859-1_UTF-8':\r
268             $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);\r
269             $escaped_data = utf8_encode($escaped_data);\r
270             break;\r
271         case 'ISO-8859-1_ISO-8859-1':\r
272         case 'US-ASCII_US-ASCII':\r
273         case 'US-ASCII_UTF-8':\r
274         case 'US-ASCII_':\r
275         case 'US-ASCII_ISO-8859-1':\r
276         case 'UTF-8_UTF-8':\r
277         //case 'CP1252_CP1252':\r
278             $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);\r
279             break;\r
280         case 'UTF-8_':\r
281         case 'UTF-8_US-ASCII':\r
282         case 'UTF-8_ISO-8859-1':\r
283 // NB: this will choke on invalid UTF-8, going most likely beyond EOF\r
284 $escaped_data = '';\r
285 // be kind to users creating string xmlrpcvals out of different php types\r
286 $data = (string) $data;\r
287 $ns = strlen ($data);\r
288 for ($nn = 0; $nn < $ns; $nn++)\r
289 {\r
290     $ch = $data[$nn];\r
291     $ii = ord($ch);\r
292     //1 7 0bbbbbbb (127)\r
293     if ($ii < 128)\r
294     {\r
295         /// @todo shall we replace this with a (supposedly) faster str_replace?\r
296         switch($ii){\r
297             case 34:\r
298                 $escaped_data .= '&quot;';\r
299                 break;\r
300             case 38:\r
301                 $escaped_data .= '&amp;';\r
302                 break;\r
303             case 39:\r
304                 $escaped_data .= '&apos;';\r
305                 break;\r
306             case 60:\r
307                 $escaped_data .= '&lt;';\r
308                 break;\r
309             case 62:\r
310                 $escaped_data .= '&gt;';\r
311                 break;\r
312             default:\r
313                 $escaped_data .= $ch;\r
314         } // switch\r
315     }\r
316     //2 11 110bbbbb 10bbbbbb (2047)\r
317     else if ($ii>>5 == 6)\r
318     {\r
319         $b1 = ($ii & 31);\r
320         $ii = ord($data[$nn+1]);\r
321         $b2 = ($ii & 63);\r
322         $ii = ($b1 * 64) + $b2;\r
323         $ent = sprintf ('&#%d;', $ii);\r
324         $escaped_data .= $ent;\r
325         $nn += 1;\r
326     }\r
327     //3 16 1110bbbb 10bbbbbb 10bbbbbb\r
328     else if ($ii>>4 == 14)\r
329     {\r
330         $b1 = ($ii & 15);\r
331         $ii = ord($data[$nn+1]);\r
332         $b2 = ($ii & 63);\r
333         $ii = ord($data[$nn+2]);\r
334         $b3 = ($ii & 63);\r
335         $ii = ((($b1 * 64) + $b2) * 64) + $b3;\r
336         $ent = sprintf ('&#%d;', $ii);\r
337         $escaped_data .= $ent;\r
338         $nn += 2;\r
339     }\r
340     //4 21 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb\r
341     else if ($ii>>3 == 30)\r
342     {\r
343         $b1 = ($ii & 7);\r
344         $ii = ord($data[$nn+1]);\r
345         $b2 = ($ii & 63);\r
346         $ii = ord($data[$nn+2]);\r
347         $b3 = ($ii & 63);\r
348         $ii = ord($data[$nn+3]);\r
349         $b4 = ($ii & 63);\r
350         $ii = ((((($b1 * 64) + $b2) * 64) + $b3) * 64) + $b4;\r
351         $ent = sprintf ('&#%d;', $ii);\r
352         $escaped_data .= $ent;\r
353         $nn += 3;\r
354     }\r
355 }\r
356             break;\r
357 /*\r
358         case 'CP1252_':\r
359         case 'CP1252_US-ASCII':\r
360             $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);\r
361             $escaped_data = str_replace($GLOBALS['xml_iso88591_Entities']['in'], $GLOBALS['xml_iso88591_Entities']['out'], $escaped_data);\r
362             $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data);\r
363             break;\r
364         case 'CP1252_UTF-8':\r
365             $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);\r
366             /// @todo we could use real UTF8 chars here instead of xml entities... (note that utf_8 encode all allone will NOT convert them)\r
367             $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data);\r
368             $escaped_data = utf8_encode($escaped_data);\r
369             break;\r
370         case 'CP1252_ISO-8859-1':\r
371             $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);\r
372             // we might as well replave all funky chars with a '?' here, but we are kind and leave it to the receiving application layer to decide what to do with these weird entities...\r
373             $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data);\r
374             break;\r
375 */\r
376         default:\r
377             $escaped_data = '';\r
378             error_log("Converting from $src_encoding to $dest_encoding: not supported...");\r
379     }\r
380     return $escaped_data;\r
381 }\r
382 \r
383 /// xml parser handler function for opening element tags\r
384 function xmlrpc_se($parser, $name, $attrs, $accept_single_vals=false)\r
385 {\r
386     $xmlrpc = Phpxmlrpc::instance();\r
387     // if invalid xmlrpc already detected, skip all processing\r
388     if ($xmlrpc->_xh['isf'] < 2)\r
389     {\r
390         // check for correct element nesting\r
391         // top level element can only be of 2 types\r
392         /// @todo optimization creep: save this check into a bool variable, instead of using count() every time:\r
393         ///       there is only a single top level element in xml anyway\r
394         if (count($xmlrpc->_xh['stack']) == 0)\r
395         {\r
396             if ($name != 'METHODRESPONSE' && $name != 'METHODCALL' && (\r
397                 $name != 'VALUE' && !$accept_single_vals))\r
398             {\r
399                 $xmlrpc->_xh['isf'] = 2;\r
400                 $xmlrpc->_xh['isf_reason'] = 'missing top level xmlrpc element';\r
401                 return;\r
402             }\r
403             else\r
404             {\r
405                 $xmlrpc->_xh['rt'] = strtolower($name);\r
406                 $xmlrpc->_xh['rt'] = strtolower($name);\r
407             }\r
408         }\r
409         else\r
410         {\r
411             // not top level element: see if parent is OK\r
412             $parent = end($xmlrpc->_xh['stack']);\r
413             if (!array_key_exists($name, $xmlrpc->xmlrpc_valid_parents) || !in_array($parent, $xmlrpc->xmlrpc_valid_parents[$name]))\r
414             {\r
415                 $xmlrpc->_xh['isf'] = 2;\r
416                 $xmlrpc->_xh['isf_reason'] = "xmlrpc element $name cannot be child of $parent";\r
417                 return;\r
418             }\r
419         }\r
420 \r
421         switch($name)\r
422         {\r
423             // optimize for speed switch cases: most common cases first\r
424             case 'VALUE':\r
425                 /// @todo we could check for 2 VALUE elements inside a MEMBER or PARAM element\r
426                 $xmlrpc->_xh['vt']='value'; // indicator: no value found yet\r
427                 $xmlrpc->_xh['ac']='';\r
428                 $xmlrpc->_xh['lv']=1;\r
429                 $xmlrpc->_xh['php_class']=null;\r
430                 break;\r
431             case 'I4':\r
432             case 'INT':\r
433             case 'STRING':\r
434             case 'BOOLEAN':\r
435             case 'DOUBLE':\r
436             case 'DATETIME.ISO8601':\r
437             case 'BASE64':\r
438                 if ($xmlrpc->_xh['vt']!='value')\r
439                 {\r
440                     //two data elements inside a value: an error occurred!\r
441                     $xmlrpc->_xh['isf'] = 2;\r
442                     $xmlrpc->_xh['isf_reason'] = "$name element following a {$xmlrpc->_xh['vt']} element inside a single value";\r
443                     return;\r
444                 }\r
445                 $xmlrpc->_xh['ac']=''; // reset the accumulator\r
446                 break;\r
447             case 'STRUCT':\r
448             case 'ARRAY':\r
449                 if ($xmlrpc->_xh['vt']!='value')\r
450                 {\r
451                     //two data elements inside a value: an error occurred!\r
452                     $xmlrpc->_xh['isf'] = 2;\r
453                     $xmlrpc->_xh['isf_reason'] = "$name element following a {$xmlrpc->_xh['vt']} element inside a single value";\r
454                     return;\r
455                 }\r
456                 // create an empty array to hold child values, and push it onto appropriate stack\r
457                 $cur_val = array();\r
458                 $cur_val['values'] = array();\r
459                 $cur_val['type'] = $name;\r
460                 // check for out-of-band information to rebuild php objs\r
461                 // and in case it is found, save it\r
462                 if (@isset($attrs['PHP_CLASS']))\r
463                 {\r
464                     $cur_val['php_class'] = $attrs['PHP_CLASS'];\r
465                 }\r
466                 $xmlrpc->_xh['valuestack'][] = $cur_val;\r
467                 $xmlrpc->_xh['vt']='data'; // be prepared for a data element next\r
468                 break;\r
469             case 'DATA':\r
470                 if ($xmlrpc->_xh['vt']!='data')\r
471                 {\r
472                     //two data elements inside a value: an error occurred!\r
473                     $xmlrpc->_xh['isf'] = 2;\r
474                     $xmlrpc->_xh['isf_reason'] = "found two data elements inside an array element";\r
475                     return;\r
476                 }\r
477             case 'METHODCALL':\r
478             case 'METHODRESPONSE':\r
479             case 'PARAMS':\r
480                 // valid elements that add little to processing\r
481                 break;\r
482             case 'METHODNAME':\r
483             case 'NAME':\r
484                 /// @todo we could check for 2 NAME elements inside a MEMBER element\r
485                 $xmlrpc->_xh['ac']='';\r
486                 break;\r
487             case 'FAULT':\r
488                 $xmlrpc->_xh['isf']=1;\r
489                 break;\r
490             case 'MEMBER':\r
491                 $xmlrpc->_xh['valuestack'][count($xmlrpc->_xh['valuestack'])-1]['name']=''; // set member name to null, in case we do not find in the xml later on\r
492                 //$xmlrpc->_xh['ac']='';\r
493                 // Drop trough intentionally\r
494             case 'PARAM':\r
495                 // clear value type, so we can check later if no value has been passed for this param/member\r
496                 $xmlrpc->_xh['vt']=null;\r
497                 break;\r
498             case 'NIL':\r
499             case 'EX:NIL':\r
500                 if ($xmlrpc->xmlrpc_null_extension)\r
501                 {\r
502                     if ($xmlrpc->_xh['vt']!='value')\r
503                     {\r
504                         //two data elements inside a value: an error occurred!\r
505                         $xmlrpc->_xh['isf'] = 2;\r
506                         $xmlrpc->_xh['isf_reason'] = "$name element following a {$xmlrpc->_xh['vt']} element inside a single value";\r
507                         return;\r
508                     }\r
509                     $xmlrpc->_xh['ac']=''; // reset the accumulator\r
510                     break;\r
511                 }\r
512                 // we do not support the <NIL/> extension, so\r
513                 // drop through intentionally\r
514             default:\r
515                 /// INVALID ELEMENT: RAISE ISF so that it is later recognized!!!\r
516                 $xmlrpc->_xh['isf'] = 2;\r
517                 $xmlrpc->_xh['isf_reason'] = "found not-xmlrpc xml element $name";\r
518                 break;\r
519         }\r
520 \r
521         // Save current element name to stack, to validate nesting\r
522         $xmlrpc->_xh['stack'][] = $name;\r
523 \r
524         /// @todo optimization creep: move this inside the big switch() above\r
525         if($name!='VALUE')\r
526         {\r
527             $xmlrpc->_xh['lv']=0;\r
528         }\r
529     }\r
530 }\r
531 \r
532 /// Used in decoding xml chunks that might represent single xmlrpc values\r
533 function xmlrpc_se_any($parser, $name, $attrs)\r
534 {\r
535     xmlrpc_se($parser, $name, $attrs, true);\r
536 }\r
537 \r
538 /// xml parser handler function for close element tags\r
539 function xmlrpc_ee($parser, $name, $rebuild_xmlrpcvals = true)\r
540 {\r
541     $xmlrpc = Phpxmlrpc::instance();\r
542 \r
543     if ($xmlrpc->_xh['isf'] < 2)\r
544     {\r
545         // push this element name from stack\r
546         // NB: if XML validates, correct opening/closing is guaranteed and\r
547         // we do not have to check for $name == $curr_elem.\r
548         // we also checked for proper nesting at start of elements...\r
549         $curr_elem = array_pop($xmlrpc->_xh['stack']);\r
550 \r
551         switch($name)\r
552         {\r
553             case 'VALUE':\r
554                 // This if() detects if no scalar was inside <VALUE></VALUE>\r
555                 if ($xmlrpc->_xh['vt']=='value')\r
556                 {\r
557                     $xmlrpc->_xh['value']=$xmlrpc->_xh['ac'];\r
558                     $xmlrpc->_xh['vt']=$xmlrpc->xmlrpcString;\r
559                 }\r
560 \r
561                 if ($rebuild_xmlrpcvals)\r
562                 {\r
563                     // build the xmlrpc val out of the data received, and substitute it\r
564                     $temp = new xmlrpcval($xmlrpc->_xh['value'], $xmlrpc->_xh['vt']);\r
565                     // in case we got info about underlying php class, save it\r
566                     // in the object we're rebuilding\r
567                     if (isset($xmlrpc->_xh['php_class']))\r
568                         $temp->_php_class = $xmlrpc->_xh['php_class'];\r
569                     // check if we are inside an array or struct:\r
570                     // if value just built is inside an array, let's move it into array on the stack\r
571                     $vscount = count($xmlrpc->_xh['valuestack']);\r
572                     if ($vscount && $xmlrpc->_xh['valuestack'][$vscount-1]['type']=='ARRAY')\r
573                     {\r
574                         $xmlrpc->_xh['valuestack'][$vscount-1]['values'][] = $temp;\r
575                     }\r
576                     else\r
577                     {\r
578                         $xmlrpc->_xh['value'] = $temp;\r
579                     }\r
580                 }\r
581                 else\r
582                 {\r
583                     /// @todo this needs to treat correctly php-serialized objects,\r
584                     /// since std deserializing is done by php_xmlrpc_decode,\r
585                     /// which we will not be calling...\r
586                     if (isset($xmlrpc->_xh['php_class']))\r
587                     {\r
588                     }\r
589 \r
590                     // check if we are inside an array or struct:\r
591                     // if value just built is inside an array, let's move it into array on the stack\r
592                     $vscount = count($xmlrpc->_xh['valuestack']);\r
593                     if ($vscount && $xmlrpc->_xh['valuestack'][$vscount-1]['type']=='ARRAY')\r
594                     {\r
595                         $xmlrpc->_xh['valuestack'][$vscount-1]['values'][] = $xmlrpc->_xh['value'];\r
596                     }\r
597                 }\r
598                 break;\r
599             case 'BOOLEAN':\r
600             case 'I4':\r
601             case 'INT':\r
602             case 'STRING':\r
603             case 'DOUBLE':\r
604             case 'DATETIME.ISO8601':\r
605             case 'BASE64':\r
606                 $xmlrpc->_xh['vt']=strtolower($name);\r
607                 /// @todo: optimization creep - remove the if/elseif cycle below\r
608                 /// since the case() in which we are already did that\r
609                 if ($name=='STRING')\r
610                 {\r
611                     $xmlrpc->_xh['value']=$xmlrpc->_xh['ac'];\r
612                 }\r
613                 elseif ($name=='DATETIME.ISO8601')\r
614                 {\r
615                     if (!preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $xmlrpc->_xh['ac']))\r
616                     {\r
617                         error_log('XML-RPC: invalid value received in DATETIME: '.$xmlrpc->_xh['ac']);\r
618                     }\r
619                     $xmlrpc->_xh['vt']=$xmlrpc->xmlrpcDateTime;\r
620                     $xmlrpc->_xh['value']=$xmlrpc->_xh['ac'];\r
621                 }\r
622                 elseif ($name=='BASE64')\r
623                 {\r
624                     /// @todo check for failure of base64 decoding / catch warnings\r
625                     $xmlrpc->_xh['value']=base64_decode($xmlrpc->_xh['ac']);\r
626                 }\r
627                 elseif ($name=='BOOLEAN')\r
628                 {\r
629                     // special case here: we translate boolean 1 or 0 into PHP\r
630                     // constants true or false.\r
631                     // Strings 'true' and 'false' are accepted, even though the\r
632                     // spec never mentions them (see eg. Blogger api docs)\r
633                     // NB: this simple checks helps a lot sanitizing input, ie no\r
634                     // security problems around here\r
635                     if ($xmlrpc->_xh['ac']=='1' || strcasecmp($xmlrpc->_xh['ac'], 'true') == 0)\r
636                     {\r
637                         $xmlrpc->_xh['value']=true;\r
638                     }\r
639                     else\r
640                     {\r
641                         // log if receiveing something strange, even though we set the value to false anyway\r
642                         if ($xmlrpc->_xh['ac']!='0' && strcasecmp($xmlrpc->_xh['ac'], 'false') != 0)\r
643                             error_log('XML-RPC: invalid value received in BOOLEAN: '.$xmlrpc->_xh['ac']);\r
644                         $xmlrpc->_xh['value']=false;\r
645                     }\r
646                 }\r
647                 elseif ($name=='DOUBLE')\r
648                 {\r
649                     // we have a DOUBLE\r
650                     // we must check that only 0123456789-.<space> are characters here\r
651                     // NOTE: regexp could be much stricter than this...\r
652                     if (!preg_match('/^[+-eE0123456789 \t.]+$/', $xmlrpc->_xh['ac']))\r
653                     {\r
654                         /// @todo: find a better way of throwing an error than this!\r
655                         error_log('XML-RPC: non numeric value received in DOUBLE: '.$xmlrpc->_xh['ac']);\r
656                         $xmlrpc->_xh['value']='ERROR_NON_NUMERIC_FOUND';\r
657                     }\r
658                     else\r
659                     {\r
660                         // it's ok, add it on\r
661                         $xmlrpc->_xh['value']=(double)$xmlrpc->_xh['ac'];\r
662                     }\r
663                 }\r
664                 else\r
665                 {\r
666                     // we have an I4/INT\r
667                     // we must check that only 0123456789-<space> are characters here\r
668                     if (!preg_match('/^[+-]?[0123456789 \t]+$/', $xmlrpc->_xh['ac']))\r
669                     {\r
670                         /// @todo find a better way of throwing an error than this!\r
671                         error_log('XML-RPC: non numeric value received in INT: '.$xmlrpc->_xh['ac']);\r
672                         $xmlrpc->_xh['value']='ERROR_NON_NUMERIC_FOUND';\r
673                     }\r
674                     else\r
675                     {\r
676                         // it's ok, add it on\r
677                         $xmlrpc->_xh['value']=(int)$xmlrpc->_xh['ac'];\r
678                     }\r
679                 }\r
680                 //$xmlrpc->_xh['ac']=''; // is this necessary?\r
681                 $xmlrpc->_xh['lv']=3; // indicate we've found a value\r
682                 break;\r
683             case 'NAME':\r
684                 $xmlrpc->_xh['valuestack'][count($xmlrpc->_xh['valuestack'])-1]['name'] = $xmlrpc->_xh['ac'];\r
685                 break;\r
686             case 'MEMBER':\r
687                 //$xmlrpc->_xh['ac']=''; // is this necessary?\r
688                 // add to array in the stack the last element built,\r
689                 // unless no VALUE was found\r
690                 if ($xmlrpc->_xh['vt'])\r
691                 {\r
692                     $vscount = count($xmlrpc->_xh['valuestack']);\r
693                     $xmlrpc->_xh['valuestack'][$vscount-1]['values'][$xmlrpc->_xh['valuestack'][$vscount-1]['name']] = $xmlrpc->_xh['value'];\r
694                 } else\r
695                     error_log('XML-RPC: missing VALUE inside STRUCT in received xml');\r
696                 break;\r
697             case 'DATA':\r
698                 //$xmlrpc->_xh['ac']=''; // is this necessary?\r
699                 $xmlrpc->_xh['vt']=null; // reset this to check for 2 data elements in a row - even if they're empty\r
700                 break;\r
701             case 'STRUCT':\r
702             case 'ARRAY':\r
703                 // fetch out of stack array of values, and promote it to current value\r
704                 $curr_val = array_pop($xmlrpc->_xh['valuestack']);\r
705                 $xmlrpc->_xh['value'] = $curr_val['values'];\r
706                 $xmlrpc->_xh['vt']=strtolower($name);\r
707                 if (isset($curr_val['php_class']))\r
708                 {\r
709                     $xmlrpc->_xh['php_class'] = $curr_val['php_class'];\r
710                 }\r
711                 break;\r
712             case 'PARAM':\r
713                 // add to array of params the current value,\r
714                 // unless no VALUE was found\r
715                 if ($xmlrpc->_xh['vt'])\r
716                 {\r
717                     $xmlrpc->_xh['params'][]=$xmlrpc->_xh['value'];\r
718                     $xmlrpc->_xh['pt'][]=$xmlrpc->_xh['vt'];\r
719                 }\r
720                 else\r
721                     error_log('XML-RPC: missing VALUE inside PARAM in received xml');\r
722                 break;\r
723             case 'METHODNAME':\r
724                 $xmlrpc->_xh['method']=preg_replace('/^[\n\r\t ]+/', '', $xmlrpc->_xh['ac']);\r
725                 break;\r
726             case 'NIL':\r
727             case 'EX:NIL':\r
728                 if ($xmlrpc->xmlrpc_null_extension)\r
729                 {\r
730                     $xmlrpc->_xh['vt']='null';\r
731                     $xmlrpc->_xh['value']=null;\r
732                     $xmlrpc->_xh['lv']=3;\r
733                     break;\r
734                 }\r
735                 // drop through intentionally if nil extension not enabled\r
736             case 'PARAMS':\r
737             case 'FAULT':\r
738             case 'METHODCALL':\r
739             case 'METHORESPONSE':\r
740                 break;\r
741             default:\r
742                 // End of INVALID ELEMENT!\r
743                 // shall we add an assert here for unreachable code???\r
744                 break;\r
745         }\r
746     }\r
747 }\r
748 \r
749 /// Used in decoding xmlrpc requests/responses without rebuilding xmlrpc values\r
750 function xmlrpc_ee_fast($parser, $name)\r
751 {\r
752     xmlrpc_ee($parser, $name, false);\r
753 }\r
754 \r
755 /// xml parser handler function for character data\r
756 function xmlrpc_cd($parser, $data)\r
757 {\r
758     $xmlrpc = Phpxmlrpc::instance();\r
759     // skip processing if xml fault already detected\r
760     if ($xmlrpc->_xh['isf'] < 2)\r
761     {\r
762         // "lookforvalue==3" means that we've found an entire value\r
763         // and should discard any further character data\r
764         if($xmlrpc->_xh['lv']!=3)\r
765         {\r
766             // G. Giunta 2006-08-23: useless change of 'lv' from 1 to 2\r
767             //if($xmlrpc->_xh['lv']==1)\r
768             //{\r
769                 // if we've found text and we're just in a <value> then\r
770                 // say we've found a value\r
771                 //$xmlrpc->_xh['lv']=2;\r
772             //}\r
773             // we always initialize the accumulator before starting parsing, anyway...\r
774             //if(!@isset($xmlrpc->_xh['ac']))\r
775             //{\r
776             //  $xmlrpc->_xh['ac'] = '';\r
777             //}\r
778             $xmlrpc->_xh['ac'].=$data;\r
779         }\r
780     }\r
781 }\r
782 \r
783 /// xml parser handler function for 'other stuff', ie. not char data or\r
784 /// element start/end tag. In fact it only gets called on unknown entities...\r
785 function xmlrpc_dh($parser, $data)\r
786 {\r
787     $xmlrpc = Phpxmlrpc::instance();\r
788     // skip processing if xml fault already detected\r
789     if ($xmlrpc->_xh['isf'] < 2)\r
790     {\r
791         if(substr($data, 0, 1) == '&' && substr($data, -1, 1) == ';')\r
792         {\r
793             // G. Giunta 2006-08-25: useless change of 'lv' from 1 to 2\r
794             //if($xmlrpc->_xh['lv']==1)\r
795             //{\r
796             //  $xmlrpc->_xh['lv']=2;\r
797             //}\r
798             $xmlrpc->_xh['ac'].=$data;\r
799         }\r
800     }\r
801     return true;\r
802 }\r
803 \r
804 // date helpers\r
805 \r
806 /**\r
807  * Given a timestamp, return the corresponding ISO8601 encoded string.\r
808  *\r
809  * Really, timezones ought to be supported\r
810  * but the XML-RPC spec says:\r
811  *\r
812  * "Don't assume a timezone. It should be specified by the server in its\r
813  * documentation what assumptions it makes about timezones."\r
814  *\r
815  * These routines always assume localtime unless\r
816  * $utc is set to 1, in which case UTC is assumed\r
817  * and an adjustment for locale is made when encoding\r
818  *\r
819  * @param int $timet (timestamp)\r
820  * @param int $utc (0 or 1)\r
821  * @return string\r
822  */\r
823 function iso8601_encode($timet, $utc=0)\r
824 {\r
825     if(!$utc)\r
826     {\r
827         $t=strftime("%Y%m%dT%H:%M:%S", $timet);\r
828     }\r
829     else\r
830     {\r
831         if(function_exists('gmstrftime'))\r
832         {\r
833             // gmstrftime doesn't exist in some versions\r
834             // of PHP\r
835             $t=gmstrftime("%Y%m%dT%H:%M:%S", $timet);\r
836         }\r
837         else\r
838         {\r
839             $t=strftime("%Y%m%dT%H:%M:%S", $timet-date('Z'));\r
840         }\r
841     }\r
842     return $t;\r
843 }\r
844 \r
845 /**\r
846  * Given an ISO8601 date string, return a timet in the localtime, or UTC\r
847  * @param string $idate\r
848  * @param int $utc either 0 or 1\r
849  * @return int (datetime)\r
850  */\r
851 function iso8601_decode($idate, $utc=0)\r
852 {\r
853     $t=0;\r
854     if(preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})/', $idate, $regs))\r
855     {\r
856         if($utc)\r
857         {\r
858             $t=gmmktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);\r
859         }\r
860         else\r
861         {\r
862             $t=mktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);\r
863         }\r
864     }\r
865     return $t;\r
866 }\r
867 \r
868 /**\r
869  * Takes an xmlrpc value in PHP xmlrpcval object format and translates it into native PHP types.\r
870  *\r
871  * Works with xmlrpc message objects as input, too.\r
872  *\r
873  * Given proper options parameter, can rebuild generic php object instances\r
874  * (provided those have been encoded to xmlrpc format using a corresponding\r
875  * option in php_xmlrpc_encode())\r
876  * PLEASE NOTE that rebuilding php objects involves calling their constructor function.\r
877  * This means that the remote communication end can decide which php code will\r
878  * get executed on your server, leaving the door possibly open to 'php-injection'\r
879  * style of attacks (provided you have some classes defined on your server that\r
880  * might wreak havoc if instances are built outside an appropriate context).\r
881  * Make sure you trust the remote server/client before eanbling this!\r
882  *\r
883  * @author Dan Libby (dan@libby.com)\r
884  *\r
885  * @param xmlrpcval $xmlrpc_val\r
886  * @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\r
887  * @return mixed\r
888  */\r
889 function php_xmlrpc_decode($xmlrpc_val, $options=array())\r
890 {\r
891     switch($xmlrpc_val->kindOf())\r
892     {\r
893         case 'scalar':\r
894             if (in_array('extension_api', $options))\r
895             {\r
896                 reset($xmlrpc_val->me);\r
897                 list($typ,$val) = each($xmlrpc_val->me);\r
898                 switch ($typ)\r
899                 {\r
900                     case 'dateTime.iso8601':\r
901                         $xmlrpc_val->scalar = $val;\r
902                         $xmlrpc_val->xmlrpc_type = 'datetime';\r
903                         $xmlrpc_val->timestamp = iso8601_decode($val);\r
904                         return $xmlrpc_val;\r
905                     case 'base64':\r
906                         $xmlrpc_val->scalar = $val;\r
907                         $xmlrpc_val->type = $typ;\r
908                         return $xmlrpc_val;\r
909                     default:\r
910                         return $xmlrpc_val->scalarval();\r
911                 }\r
912             }\r
913             if (in_array('dates_as_objects', $options) && $xmlrpc_val->scalartyp() == 'dateTime.iso8601')\r
914             {\r
915                 // we return a Datetime object instead of a string\r
916                 // since now the constructor of xmlrpcval accepts safely strings, ints and datetimes,\r
917                 // we cater to all 3 cases here\r
918                 $out = $xmlrpc_val->scalarval();\r
919                 if (is_string($out))\r
920                 {\r
921                     $out = strtotime($out);\r
922                 }\r
923                 if (is_int($out))\r
924                 {\r
925                     $result = new Datetime();\r
926                     $result->setTimestamp($out);\r
927                     return $result;\r
928                 }\r
929                 elseif (is_a($out, 'Datetime'))\r
930                 {\r
931                     return $out;\r
932                 }\r
933             }\r
934             return $xmlrpc_val->scalarval();\r
935         case 'array':\r
936             $size = $xmlrpc_val->arraysize();\r
937             $arr = array();\r
938             for($i = 0; $i < $size; $i++)\r
939             {\r
940                 $arr[] = php_xmlrpc_decode($xmlrpc_val->arraymem($i), $options);\r
941             }\r
942             return $arr;\r
943         case 'struct':\r
944             $xmlrpc_val->structreset();\r
945             // If user said so, try to rebuild php objects for specific struct vals.\r
946             /// @todo should we raise a warning for class not found?\r
947             // shall we check for proper subclass of xmlrpcval instead of\r
948             // presence of _php_class to detect what we can do?\r
949             if (in_array('decode_php_objs', $options) && $xmlrpc_val->_php_class != ''\r
950                 && class_exists($xmlrpc_val->_php_class))\r
951             {\r
952                 $obj = @new $xmlrpc_val->_php_class;\r
953                 while(list($key,$value)=$xmlrpc_val->structeach())\r
954                 {\r
955                     $obj->$key = php_xmlrpc_decode($value, $options);\r
956                 }\r
957                 return $obj;\r
958             }\r
959             else\r
960             {\r
961                 $arr = array();\r
962                 while(list($key,$value)=$xmlrpc_val->structeach())\r
963                 {\r
964                     $arr[$key] = php_xmlrpc_decode($value, $options);\r
965                 }\r
966                 return $arr;\r
967             }\r
968         case 'msg':\r
969             $paramcount = $xmlrpc_val->getNumParams();\r
970             $arr = array();\r
971             for($i = 0; $i < $paramcount; $i++)\r
972             {\r
973                 $arr[] = php_xmlrpc_decode($xmlrpc_val->getParam($i));\r
974             }\r
975             return $arr;\r
976         }\r
977 }\r
978 \r
979 // This constant left here only for historical reasons...\r
980 // it was used to decide if we have to define xmlrpc_encode on our own, but\r
981 // we do not do it anymore\r
982 if(function_exists('xmlrpc_decode'))\r
983 {\r
984     define('XMLRPC_EPI_ENABLED','1');\r
985 }\r
986 else\r
987 {\r
988     define('XMLRPC_EPI_ENABLED','0');\r
989 }\r
990 \r
991 /**\r
992  * Takes native php types and encodes them into xmlrpc PHP object format.\r
993  * It will not re-encode xmlrpcval objects.\r
994  *\r
995  * Feature creep -- could support more types via optional type argument\r
996  * (string => datetime support has been added, ??? => base64 not yet)\r
997  *\r
998  * If given a proper options parameter, php object instances will be encoded\r
999  * into 'special' xmlrpc values, that can later be decoded into php objects\r
1000  * by calling php_xmlrpc_decode() with a corresponding option\r
1001  *\r
1002  * @author Dan Libby (dan@libby.com)\r
1003  *\r
1004  * @param mixed $php_val the value to be converted into an xmlrpcval object\r
1005  * @param array $options        can include 'encode_php_objs', 'auto_dates', 'null_extension' or 'extension_api'\r
1006  * @return xmlrpcval\r
1007  */\r
1008 function php_xmlrpc_encode($php_val, $options=array())\r
1009 {\r
1010     $xmlrpc = Phpxmlrpc::instance();\r
1011     $type = gettype($php_val);\r
1012     switch($type)\r
1013     {\r
1014         case 'string':\r
1015             if (in_array('auto_dates', $options) && preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $php_val))\r
1016                 $xmlrpc_val = new xmlrpcval($php_val, $xmlrpc->xmlrpcDateTime);\r
1017             else\r
1018                 $xmlrpc_val = new xmlrpcval($php_val, $xmlrpc->xmlrpcString);\r
1019             break;\r
1020         case 'integer':\r
1021             $xmlrpc_val = new xmlrpcval($php_val, $xmlrpc->xmlrpcInt);\r
1022             break;\r
1023         case 'double':\r
1024             $xmlrpc_val = new xmlrpcval($php_val, $xmlrpc->xmlrpcDouble);\r
1025             break;\r
1026             // <G_Giunta_2001-02-29>\r
1027             // Add support for encoding/decoding of booleans, since they are supported in PHP\r
1028         case 'boolean':\r
1029             $xmlrpc_val = new xmlrpcval($php_val, $xmlrpc->xmlrpcBoolean);\r
1030             break;\r
1031             // </G_Giunta_2001-02-29>\r
1032         case 'array':\r
1033             // PHP arrays can be encoded to either xmlrpc structs or arrays,\r
1034             // depending on wheter they are hashes or plain 0..n integer indexed\r
1035             // A shorter one-liner would be\r
1036             // $tmp = array_diff(array_keys($php_val), range(0, count($php_val)-1));\r
1037             // but execution time skyrockets!\r
1038             $j = 0;\r
1039             $arr = array();\r
1040             $ko = false;\r
1041             foreach($php_val as $key => $val)\r
1042             {\r
1043                 $arr[$key] = php_xmlrpc_encode($val, $options);\r
1044                 if(!$ko && $key !== $j)\r
1045                 {\r
1046                     $ko = true;\r
1047                 }\r
1048                 $j++;\r
1049             }\r
1050             if($ko)\r
1051             {\r
1052                 $xmlrpc_val = new xmlrpcval($arr, $xmlrpc->xmlrpcStruct);\r
1053             }\r
1054             else\r
1055             {\r
1056                 $xmlrpc_val = new xmlrpcval($arr, $xmlrpc->xmlrpcArray);\r
1057             }\r
1058             break;\r
1059         case 'object':\r
1060             if(is_a($php_val, 'xmlrpcval'))\r
1061             {\r
1062                 $xmlrpc_val = $php_val;\r
1063             }\r
1064             else if(is_a($php_val, 'DateTime'))\r
1065             {\r
1066                 $xmlrpc_val = new xmlrpcval($php_val->format('Ymd\TH:i:s'), $xmlrpc->xmlrpcStruct);\r
1067             }\r
1068             else\r
1069             {\r
1070                 $arr = array();\r
1071                 reset($php_val);\r
1072                 while(list($k,$v) = each($php_val))\r
1073                 {\r
1074                     $arr[$k] = php_xmlrpc_encode($v, $options);\r
1075                 }\r
1076                 $xmlrpc_val = new xmlrpcval($arr, $xmlrpc->xmlrpcStruct);\r
1077                 if (in_array('encode_php_objs', $options))\r
1078                 {\r
1079                     // let's save original class name into xmlrpcval:\r
1080                     // might be useful later on...\r
1081                     $xmlrpc_val->_php_class = get_class($php_val);\r
1082                 }\r
1083             }\r
1084             break;\r
1085         case 'NULL':\r
1086             if (in_array('extension_api', $options))\r
1087             {\r
1088                 $xmlrpc_val = new xmlrpcval('', $xmlrpc->xmlrpcString);\r
1089             }\r
1090             else if (in_array('null_extension', $options))\r
1091             {\r
1092                 $xmlrpc_val = new xmlrpcval('', $xmlrpc->xmlrpcNull);\r
1093             }\r
1094             else\r
1095             {\r
1096                 $xmlrpc_val = new xmlrpcval();\r
1097             }\r
1098             break;\r
1099         case 'resource':\r
1100             if (in_array('extension_api', $options))\r
1101             {\r
1102                 $xmlrpc_val = new xmlrpcval((int)$php_val, $xmlrpc->xmlrpcInt);\r
1103             }\r
1104             else\r
1105             {\r
1106                 $xmlrpc_val = new xmlrpcval();\r
1107             }\r
1108         // catch "user function", "unknown type"\r
1109         default:\r
1110             // giancarlo pinerolo <ping@alt.it>\r
1111             // it has to return\r
1112             // an empty object in case, not a boolean.\r
1113             $xmlrpc_val = new xmlrpcval();\r
1114             break;\r
1115         }\r
1116         return $xmlrpc_val;\r
1117 }\r
1118 \r
1119 /**\r
1120  * Convert the xml representation of a method response, method request or single\r
1121  * xmlrpc value into the appropriate object (a.k.a. deserialize)\r
1122  * @param string $xml_val\r
1123  * @param array $options\r
1124  * @return mixed false on error, or an instance of either xmlrpcval, xmlrpcmsg or xmlrpcresp\r
1125  */\r
1126 function php_xmlrpc_decode_xml($xml_val, $options=array())\r
1127 {\r
1128     $xmlrpc = Phpxmlrpc::instance();\r
1129 \r
1130     $xmlrpc->_xh = array();\r
1131     $xmlrpc->_xh['ac'] = '';\r
1132     $xmlrpc->_xh['stack'] = array();\r
1133     $xmlrpc->_xh['valuestack'] = array();\r
1134     $xmlrpc->_xh['params'] = array();\r
1135     $xmlrpc->_xh['pt'] = array();\r
1136     $xmlrpc->_xh['isf'] = 0;\r
1137     $xmlrpc->_xh['isf_reason'] = '';\r
1138     $xmlrpc->_xh['method'] = false;\r
1139     $xmlrpc->_xh['rt'] = '';\r
1140     /// @todo 'guestimate' encoding\r
1141     $parser = xml_parser_create();\r
1142     xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);\r
1143     // What if internal encoding is not in one of the 3 allowed?\r
1144     // we use the broadest one, ie. utf8!\r
1145     if (!in_array($xmlrpc->xmlrpc_internalencoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))\r
1146     {\r
1147         xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');\r
1148     }\r
1149     else\r
1150     {\r
1151         xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $xmlrpc->xmlrpc_internalencoding);\r
1152     }\r
1153     xml_set_element_handler($parser, 'xmlrpc_se_any', 'xmlrpc_ee');\r
1154     xml_set_character_data_handler($parser, 'xmlrpc_cd');\r
1155     xml_set_default_handler($parser, 'xmlrpc_dh');\r
1156     if(!xml_parse($parser, $xml_val, 1))\r
1157     {\r
1158         $errstr = sprintf('XML error: %s at line %d, column %d',\r
1159                     xml_error_string(xml_get_error_code($parser)),\r
1160                     xml_get_current_line_number($parser), xml_get_current_column_number($parser));\r
1161         error_log($errstr);\r
1162         xml_parser_free($parser);\r
1163         return false;\r
1164     }\r
1165     xml_parser_free($parser);\r
1166     if ($xmlrpc->_xh['isf'] > 1) // test that $xmlrpc->_xh['value'] is an obj, too???\r
1167     {\r
1168         error_log($xmlrpc->_xh['isf_reason']);\r
1169         return false;\r
1170     }\r
1171     switch ($xmlrpc->_xh['rt'])\r
1172     {\r
1173         case 'methodresponse':\r
1174             $v =& $xmlrpc->_xh['value'];\r
1175             if ($xmlrpc->_xh['isf'] == 1)\r
1176             {\r
1177                 $vc = $v->structmem('faultCode');\r
1178                 $vs = $v->structmem('faultString');\r
1179                 $r = new xmlrpcresp(0, $vc->scalarval(), $vs->scalarval());\r
1180             }\r
1181             else\r
1182             {\r
1183                 $r = new xmlrpcresp($v);\r
1184             }\r
1185             return $r;\r
1186         case 'methodcall':\r
1187             $m = new xmlrpcmsg($xmlrpc->_xh['method']);\r
1188             for($i=0; $i < count($xmlrpc->_xh['params']); $i++)\r
1189             {\r
1190                 $m->addParam($xmlrpc->_xh['params'][$i]);\r
1191             }\r
1192             return $m;\r
1193         case 'value':\r
1194             return $xmlrpc->_xh['value'];\r
1195         default:\r
1196             return false;\r
1197     }\r
1198 }\r
1199 \r
1200 /**\r
1201  * decode a string that is encoded w/ "chunked" transfer encoding\r
1202  * as defined in rfc2068 par. 19.4.6\r
1203  * code shamelessly stolen from nusoap library by Dietrich Ayala\r
1204  *\r
1205  * @param string $buffer the string to be decoded\r
1206  * @return string\r
1207  */\r
1208 function decode_chunked($buffer)\r
1209 {\r
1210     // length := 0\r
1211     $length = 0;\r
1212     $new = '';\r
1213 \r
1214     // read chunk-size, chunk-extension (if any) and crlf\r
1215     // get the position of the linebreak\r
1216     $chunkend = strpos($buffer,"\r\n") + 2;\r
1217     $temp = substr($buffer,0,$chunkend);\r
1218     $chunk_size = hexdec( trim($temp) );\r
1219     $chunkstart = $chunkend;\r
1220     while($chunk_size > 0)\r
1221     {\r
1222         $chunkend = strpos($buffer, "\r\n", $chunkstart + $chunk_size);\r
1223 \r
1224         // just in case we got a broken connection\r
1225         if($chunkend == false)\r
1226         {\r
1227             $chunk = substr($buffer,$chunkstart);\r
1228             // append chunk-data to entity-body\r
1229             $new .= $chunk;\r
1230             $length += strlen($chunk);\r
1231             break;\r
1232         }\r
1233 \r
1234         // read chunk-data and crlf\r
1235         $chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart);\r
1236         // append chunk-data to entity-body\r
1237         $new .= $chunk;\r
1238         // length := length + chunk-size\r
1239         $length += strlen($chunk);\r
1240         // read chunk-size and crlf\r
1241         $chunkstart = $chunkend + 2;\r
1242 \r
1243         $chunkend = strpos($buffer,"\r\n",$chunkstart)+2;\r
1244         if($chunkend == false)\r
1245         {\r
1246             break; //just in case we got a broken connection\r
1247         }\r
1248         $temp = substr($buffer,$chunkstart,$chunkend-$chunkstart);\r
1249         $chunk_size = hexdec( trim($temp) );\r
1250         $chunkstart = $chunkend;\r
1251     }\r
1252     return $new;\r
1253 }\r
1254 \r
1255 /**\r
1256  * xml charset encoding guessing helper function.\r
1257  * Tries to determine the charset encoding of an XML chunk received over HTTP.\r
1258  * NB: according to the spec (RFC 3023), if text/xml content-type is received over HTTP without a content-type,\r
1259  * we SHOULD assume it is strictly US-ASCII. But we try to be more tolerant of unconforming (legacy?) clients/servers,\r
1260  * which will be most probably using UTF-8 anyway...\r
1261  *\r
1262  * @param string $httpheader the http Content-type header\r
1263  * @param string $xmlchunk xml content buffer\r
1264  * @param string $encoding_prefs comma separated list of character encodings to be used as default (when mb extension is enabled)\r
1265  * @return string\r
1266  *\r
1267  * @todo explore usage of mb_http_input(): does it detect http headers + post data? if so, use it instead of hand-detection!!!\r
1268  */\r
1269 function guess_encoding($httpheader='', $xmlchunk='', $encoding_prefs=null)\r
1270 {\r
1271     $xmlrpc = Phpxmlrpc::instance();\r
1272 \r
1273     // discussion: see http://www.yale.edu/pclt/encoding/\r
1274     // 1 - test if encoding is specified in HTTP HEADERS\r
1275 \r
1276     //Details:\r
1277     // LWS:           (\13\10)?( |\t)+\r
1278     // token:         (any char but excluded stuff)+\r
1279     // quoted string: " (any char but double quotes and cointrol chars)* "\r
1280     // header:        Content-type = ...; charset=value(; ...)*\r
1281     //   where value is of type token, no LWS allowed between 'charset' and value\r
1282     // Note: we do not check for invalid chars in VALUE:\r
1283     //   this had better be done using pure ereg as below\r
1284     // Note 2: we might be removing whitespace/tabs that ought to be left in if\r
1285     //   the received charset is a quoted string. But nobody uses such charset names...\r
1286 \r
1287     /// @todo this test will pass if ANY header has charset specification, not only Content-Type. Fix it?\r
1288     $matches = array();\r
1289     if(preg_match('/;\s*charset\s*=([^;]+)/i', $httpheader, $matches))\r
1290     {\r
1291         return strtoupper(trim($matches[1], " \t\""));\r
1292     }\r
1293 \r
1294     // 2 - scan the first bytes of the data for a UTF-16 (or other) BOM pattern\r
1295     //     (source: http://www.w3.org/TR/2000/REC-xml-20001006)\r
1296     //     NOTE: actually, according to the spec, even if we find the BOM and determine\r
1297     //     an encoding, we should check if there is an encoding specified\r
1298     //     in the xml declaration, and verify if they match.\r
1299     /// @todo implement check as described above?\r
1300     /// @todo implement check for first bytes of string even without a BOM? (It sure looks harder than for cases WITH a BOM)\r
1301     if(preg_match('/^(\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\x00\x00\xFF\xFE|\xFE\xFF\x00\x00)/', $xmlchunk))\r
1302     {\r
1303         return 'UCS-4';\r
1304     }\r
1305     elseif(preg_match('/^(\xFE\xFF|\xFF\xFE)/', $xmlchunk))\r
1306     {\r
1307         return 'UTF-16';\r
1308     }\r
1309     elseif(preg_match('/^(\xEF\xBB\xBF)/', $xmlchunk))\r
1310     {\r
1311         return 'UTF-8';\r
1312     }\r
1313 \r
1314     // 3 - test if encoding is specified in the xml declaration\r
1315     // Details:\r
1316     // SPACE:         (#x20 | #x9 | #xD | #xA)+ === [ \x9\xD\xA]+\r
1317     // EQ:            SPACE?=SPACE? === [ \x9\xD\xA]*=[ \x9\xD\xA]*\r
1318     if (preg_match('/^<\?xml\s+version\s*=\s*'. "((?:\"[a-zA-Z0-9_.:-]+\")|(?:'[a-zA-Z0-9_.:-]+'))".\r
1319         '\s+encoding\s*=\s*' . "((?:\"[A-Za-z][A-Za-z0-9._-]*\")|(?:'[A-Za-z][A-Za-z0-9._-]*'))/",\r
1320         $xmlchunk, $matches))\r
1321     {\r
1322         return strtoupper(substr($matches[2], 1, -1));\r
1323     }\r
1324 \r
1325     // 4 - if mbstring is available, let it do the guesswork\r
1326     // NB: we favour finding an encoding that is compatible with what we can process\r
1327     if(extension_loaded('mbstring'))\r
1328     {\r
1329         if($encoding_prefs)\r
1330         {\r
1331             $enc = mb_detect_encoding($xmlchunk, $encoding_prefs);\r
1332         }\r
1333         else\r
1334         {\r
1335             $enc = mb_detect_encoding($xmlchunk);\r
1336         }\r
1337         // NB: mb_detect likes to call it ascii, xml parser likes to call it US_ASCII...\r
1338         // IANA also likes better US-ASCII, so go with it\r
1339         if($enc == 'ASCII')\r
1340         {\r
1341             $enc = 'US-'.$enc;\r
1342         }\r
1343         return $enc;\r
1344     }\r
1345     else\r
1346     {\r
1347         // no encoding specified: as per HTTP1.1 assume it is iso-8859-1?\r
1348         // Both RFC 2616 (HTTP 1.1) and 1945 (HTTP 1.0) clearly state that for text/xxx content types\r
1349         // this should be the standard. And we should be getting text/xml as request and response.\r
1350         // BUT we have to be backward compatible with the lib, which always used UTF-8 as default...\r
1351         return $xmlrpc->xmlrpc_defencoding;\r
1352     }\r
1353 }\r
1354 \r
1355 /**\r
1356  * Checks if a given charset encoding is present in a list of encodings or\r
1357  * if it is a valid subset of any encoding in the list\r
1358  * @param string $encoding charset to be tested\r
1359  * @param mixed $validlist comma separated list of valid charsets (or array of charsets)\r
1360  * @return bool\r
1361  */\r
1362 function is_valid_charset($encoding, $validlist)\r
1363 {\r
1364     $charset_supersets = array(\r
1365         'US-ASCII' => array ('ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4',\r
1366             'ISO-8859-5', 'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8',\r
1367             'ISO-8859-9', 'ISO-8859-10', 'ISO-8859-11', 'ISO-8859-12',\r
1368             'ISO-8859-13', 'ISO-8859-14', 'ISO-8859-15', 'UTF-8',\r
1369             'EUC-JP', 'EUC-', 'EUC-KR', 'EUC-CN')\r
1370     );\r
1371     if (is_string($validlist))\r
1372         $validlist = explode(',', $validlist);\r
1373     if (@in_array(strtoupper($encoding), $validlist))\r
1374         return true;\r
1375     else\r
1376     {\r
1377         if (array_key_exists($encoding, $charset_supersets))\r
1378             foreach ($validlist as $allowed)\r
1379                 if (in_array($allowed, $charset_supersets[$encoding]))\r
1380                     return true;\r
1381         return false;\r
1382     }\r
1383 }\r
1384 \r
1385 ?>