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