Branch oldpackages for 14.07
[14.07/packages.git] / net / nzbgetweb / files / lib / xmlrpc.inc
1 <?php
2 // by Edd Dumbill (C) 1999-2002
3 // <edd@usefulinc.com>
4 // $Id: xmlrpc.inc,v 1.174 2009/03/16 19:36:38 ggiunta Exp $
5
6 // Copyright (c) 1999,2000,2002 Edd Dumbill.
7 // All rights reserved.
8 //
9 // Redistribution and use in source and binary forms, with or without
10 // modification, are permitted provided that the following conditions
11 // are met:
12 //
13 //    * Redistributions of source code must retain the above copyright
14 //      notice, this list of conditions and the following disclaimer.
15 //
16 //    * Redistributions in binary form must reproduce the above
17 //      copyright notice, this list of conditions and the following
18 //      disclaimer in the documentation and/or other materials provided
19 //      with the distribution.
20 //
21 //    * Neither the name of the "XML-RPC for PHP" nor the names of its
22 //      contributors may be used to endorse or promote products derived
23 //      from this software without specific prior written permission.
24 //
25 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
28 // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
29 // REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
30 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31 // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34 // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
36 // OF THE POSSIBILITY OF SUCH DAMAGE.
37
38         if(!function_exists('xml_parser_create'))
39         {
40                 // For PHP 4 onward, XML functionality is always compiled-in on windows:
41                 // no more need to dl-open it. It might have been compiled out on *nix...
42                 if(strtoupper(substr(PHP_OS, 0, 3) != 'WIN'))
43                 {
44                         dl('xml.so');
45                 }
46         }
47
48         // G. Giunta 2005/01/29: declare global these variables,
49         // so that xmlrpc.inc will work even if included from within a function
50         // Milosch: 2005/08/07 - explicitly request these via $GLOBALS where used.
51         $GLOBALS['xmlrpcI4']='i4';
52         $GLOBALS['xmlrpcInt']='int';
53         $GLOBALS['xmlrpcBoolean']='boolean';
54         $GLOBALS['xmlrpcDouble']='double';
55         $GLOBALS['xmlrpcString']='string';
56         $GLOBALS['xmlrpcDateTime']='dateTime.iso8601';
57         $GLOBALS['xmlrpcBase64']='base64';
58         $GLOBALS['xmlrpcArray']='array';
59         $GLOBALS['xmlrpcStruct']='struct';
60         $GLOBALS['xmlrpcValue']='undefined';
61
62         $GLOBALS['xmlrpcTypes']=array(
63                 $GLOBALS['xmlrpcI4']       => 1,
64                 $GLOBALS['xmlrpcInt']      => 1,
65                 $GLOBALS['xmlrpcBoolean']  => 1,
66                 $GLOBALS['xmlrpcString']   => 1,
67                 $GLOBALS['xmlrpcDouble']   => 1,
68                 $GLOBALS['xmlrpcDateTime'] => 1,
69                 $GLOBALS['xmlrpcBase64']   => 1,
70                 $GLOBALS['xmlrpcArray']    => 2,
71                 $GLOBALS['xmlrpcStruct']   => 3
72         );
73
74         $GLOBALS['xmlrpc_valid_parents'] = array(
75                 'VALUE' => array('MEMBER', 'DATA', 'PARAM', 'FAULT'),
76                 'BOOLEAN' => array('VALUE'),
77                 'I4' => array('VALUE'),
78                 'INT' => array('VALUE'),
79                 'STRING' => array('VALUE'),
80                 'DOUBLE' => array('VALUE'),
81                 'DATETIME.ISO8601' => array('VALUE'),
82                 'BASE64' => array('VALUE'),
83                 'MEMBER' => array('STRUCT'),
84                 'NAME' => array('MEMBER'),
85                 'DATA' => array('ARRAY'),
86                 'ARRAY' => array('VALUE'),
87                 'STRUCT' => array('VALUE'),
88                 'PARAM' => array('PARAMS'),
89                 'METHODNAME' => array('METHODCALL'),
90                 'PARAMS' => array('METHODCALL', 'METHODRESPONSE'),
91                 'FAULT' => array('METHODRESPONSE'),
92                 'NIL' => array('VALUE'), // only used when extension activated
93                 'EX:NIL' => array('VALUE') // only used when extension activated
94         );
95
96         // define extra types for supporting NULL (useful for json or <NIL/>)
97         $GLOBALS['xmlrpcNull']='null';
98         $GLOBALS['xmlrpcTypes']['null']=1;
99
100         // Not in use anymore since 2.0. Shall we remove it?
101         /// @deprecated
102         $GLOBALS['xmlEntities']=array(
103                 'amp'  => '&',
104                 'quot' => '"',
105                 'lt'   => '<',
106                 'gt'   => '>',
107                 'apos' => "'"
108         );
109
110         // tables used for transcoding different charsets into us-ascii xml
111
112         $GLOBALS['xml_iso88591_Entities']=array();
113         $GLOBALS['xml_iso88591_Entities']['in'] = array();
114         $GLOBALS['xml_iso88591_Entities']['out'] = array();
115         for ($i = 0; $i < 32; $i++)
116         {
117                 $GLOBALS['xml_iso88591_Entities']['in'][] = chr($i);
118                 $GLOBALS['xml_iso88591_Entities']['out'][] = '&#'.$i.';';
119         }
120         for ($i = 160; $i < 256; $i++)
121         {
122                 $GLOBALS['xml_iso88591_Entities']['in'][] = chr($i);
123                 $GLOBALS['xml_iso88591_Entities']['out'][] = '&#'.$i.';';
124         }
125
126         /// @todo add to iso table the characters from cp_1252 range, i.e. 128 to 159?
127         /// These will NOT be present in true ISO-8859-1, but will save the unwary
128         /// windows user from sending junk (though no luck when reciving them...)
129   /*
130         $GLOBALS['xml_cp1252_Entities']=array();
131         for ($i = 128; $i < 160; $i++)
132         {
133                 $GLOBALS['xml_cp1252_Entities']['in'][] = chr($i);
134         }
135         $GLOBALS['xml_cp1252_Entities']['out'] = array(
136                 '&#x20AC;', '?',        '&#x201A;', '&#x0192;',
137                 '&#x201E;', '&#x2026;', '&#x2020;', '&#x2021;',
138                 '&#x02C6;', '&#x2030;', '&#x0160;', '&#x2039;',
139                 '&#x0152;', '?',        '&#x017D;', '?',
140                 '?',        '&#x2018;', '&#x2019;', '&#x201C;',
141                 '&#x201D;', '&#x2022;', '&#x2013;', '&#x2014;',
142                 '&#x02DC;', '&#x2122;', '&#x0161;', '&#x203A;',
143                 '&#x0153;', '?',        '&#x017E;', '&#x0178;'
144         );
145   */
146
147         $GLOBALS['xmlrpcerr'] = array(
148         'unknown_method'=>1,
149         'invalid_return'=>2,
150         'incorrect_params'=>3,
151         'introspect_unknown'=>4,
152         'http_error'=>5,
153         'no_data'=>6,
154         'no_ssl'=>7,
155         'curl_fail'=>8,
156         'invalid_request'=>15,
157         'no_curl'=>16,
158         'server_error'=>17,
159         'multicall_error'=>18,
160         'multicall_notstruct'=>9,
161         'multicall_nomethod'=>10,
162         'multicall_notstring'=>11,
163         'multicall_recursion'=>12,
164         'multicall_noparams'=>13,
165         'multicall_notarray'=>14,
166
167         'cannot_decompress'=>103,
168         'decompress_fail'=>104,
169         'dechunk_fail'=>105,
170         'server_cannot_decompress'=>106,
171         'server_decompress_fail'=>107
172         );
173
174         $GLOBALS['xmlrpcstr'] = array(
175         'unknown_method'=>'Unknown method',
176         'invalid_return'=>'Invalid return payload: enable debugging to examine incoming payload',
177         'incorrect_params'=>'Incorrect parameters passed to method',
178         'introspect_unknown'=>"Can't introspect: method unknown",
179         'http_error'=>"Didn't receive 200 OK from remote server.",
180         'no_data'=>'No data received from server.',
181         'no_ssl'=>'No SSL support compiled in.',
182         'curl_fail'=>'CURL error',
183         'invalid_request'=>'Invalid request payload',
184         'no_curl'=>'No CURL support compiled in.',
185         'server_error'=>'Internal server error',
186         'multicall_error'=>'Received from server invalid multicall response',
187         'multicall_notstruct'=>'system.multicall expected struct',
188         'multicall_nomethod'=>'missing methodName',
189         'multicall_notstring'=>'methodName is not a string',
190         'multicall_recursion'=>'recursive system.multicall forbidden',
191         'multicall_noparams'=>'missing params',
192         'multicall_notarray'=>'params is not an array',
193
194         'cannot_decompress'=>'Received from server compressed HTTP and cannot decompress',
195         'decompress_fail'=>'Received from server invalid compressed HTTP',
196         'dechunk_fail'=>'Received from server invalid chunked HTTP',
197         'server_cannot_decompress'=>'Received from client compressed HTTP request and cannot decompress',
198         'server_decompress_fail'=>'Received from client invalid compressed HTTP request'
199         );
200
201         // The charset encoding used by the server for received messages and
202         // by the client for received responses when received charset cannot be determined
203         // or is not supported
204         $GLOBALS['xmlrpc_defencoding']='UTF-8';
205
206         // The encoding used internally by PHP.
207         // String values received as xml will be converted to this, and php strings will be converted to xml
208         // as if having been coded with this
209         $GLOBALS['xmlrpc_internalencoding']='ISO-8859-1';
210
211         $GLOBALS['xmlrpcName']='XML-RPC for PHP';
212         $GLOBALS['xmlrpcVersion']='3.0.0.beta';
213
214         // let user errors start at 800
215         $GLOBALS['xmlrpcerruser']=800;
216         // let XML parse errors start at 100
217         $GLOBALS['xmlrpcerrxml']=100;
218
219         // formulate backslashes for escaping regexp
220         // Not in use anymore since 2.0. Shall we remove it?
221         /// @deprecated
222         $GLOBALS['xmlrpc_backslash']=chr(92).chr(92);
223
224         // set to TRUE to enable correct decoding of <NIL/> and <EX:NIL/> values
225         $GLOBALS['xmlrpc_null_extension']=false;
226
227         // set to TRUE to enable encoding of php NULL values to <EX:NIL/> instead of <NIL/>
228         $GLOBALS['xmlrpc_null_apache_encoding']=false;
229
230         // used to store state during parsing
231         // quick explanation of components:
232         //   ac - used to accumulate values
233         //   isf - used to indicate a parsing fault (2) or xmlrpcresp fault (1)
234         //   isf_reason - used for storing xmlrpcresp fault string
235         //   lv - used to indicate "looking for a value": implements
236         //        the logic to allow values with no types to be strings
237         //   params - used to store parameters in method calls
238         //   method - used to store method name
239         //   stack - array with genealogy of xml elements names:
240         //           used to validate nesting of xmlrpc elements
241         $GLOBALS['_xh']=null;
242
243         /**
244         * Convert a string to the correct XML representation in a target charset
245         * To help correct communication of non-ascii chars inside strings, regardless
246         * of the charset used when sending requests, parsing them, sending responses
247         * and parsing responses, an option is to convert all non-ascii chars present in the message
248         * into their equivalent 'charset entity'. Charset entities enumerated this way
249         * are independent of the charset encoding used to transmit them, and all XML
250         * parsers are bound to understand them.
251         * Note that in the std case we are not sending a charset encoding mime type
252         * along with http headers, so we are bound by RFC 3023 to emit strict us-ascii.
253         *
254         * @todo do a bit of basic benchmarking (strtr vs. str_replace)
255         * @todo make usage of iconv() or recode_string() or mb_string() where available
256         */
257         function xmlrpc_encode_entitites($data, $src_encoding='', $dest_encoding='')
258         {
259                 if ($src_encoding == '')
260                 {
261                         // lame, but we know no better...
262                         $src_encoding = $GLOBALS['xmlrpc_internalencoding'];
263                 }
264
265                 switch(strtoupper($src_encoding.'_'.$dest_encoding))
266                 {
267                         case 'ISO-8859-1_':
268                         case 'ISO-8859-1_US-ASCII':
269                                 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
270                                 $escaped_data = str_replace($GLOBALS['xml_iso88591_Entities']['in'], $GLOBALS['xml_iso88591_Entities']['out'], $escaped_data);
271                                 break;
272                         case 'ISO-8859-1_UTF-8':
273                                 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
274                                 $escaped_data = utf8_encode($escaped_data);
275                                 break;
276                         case 'ISO-8859-1_ISO-8859-1':
277                         case 'US-ASCII_US-ASCII':
278                         case 'US-ASCII_UTF-8':
279                         case 'US-ASCII_':
280                         case 'US-ASCII_ISO-8859-1':
281                         case 'UTF-8_UTF-8':
282                         //case 'CP1252_CP1252':
283                                 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
284                                 break;
285                         case 'UTF-8_':
286                         case 'UTF-8_US-ASCII':
287                         case 'UTF-8_ISO-8859-1':
288         // NB: this will choke on invalid UTF-8, going most likely beyond EOF
289         $escaped_data = '';
290         // be kind to users creating string xmlrpcvals out of different php types
291         $data = (string) $data;
292         $ns = strlen ($data);
293         for ($nn = 0; $nn < $ns; $nn++)
294         {
295                 $ch = $data[$nn];
296                 $ii = ord($ch);
297                 //1 7 0bbbbbbb (127)
298                 if ($ii < 128)
299                 {
300                         /// @todo shall we replace this with a (supposedly) faster str_replace?
301                         switch($ii){
302                                 case 34:
303                                         $escaped_data .= '&quot;';
304                                         break;
305                                 case 38:
306                                         $escaped_data .= '&amp;';
307                                         break;
308                                 case 39:
309                                         $escaped_data .= '&apos;';
310                                         break;
311                                 case 60:
312                                         $escaped_data .= '&lt;';
313                                         break;
314                                 case 62:
315                                         $escaped_data .= '&gt;';
316                                         break;
317                                 default:
318                                         $escaped_data .= $ch;
319                         } // switch
320                 }
321                 //2 11 110bbbbb 10bbbbbb (2047)
322                 else if ($ii>>5 == 6)
323                 {
324                         $b1 = ($ii & 31);
325                         $ii = ord($data[$nn+1]);
326                         $b2 = ($ii & 63);
327                         $ii = ($b1 * 64) + $b2;
328                         $ent = sprintf ('&#%d;', $ii);
329                         $escaped_data .= $ent;
330                         $nn += 1;
331                 }
332                 //3 16 1110bbbb 10bbbbbb 10bbbbbb
333                 else if ($ii>>4 == 14)
334                 {
335                         $b1 = ($ii & 15);
336                         $ii = ord($data[$nn+1]);
337                         $b2 = ($ii & 63);
338                         $ii = ord($data[$nn+2]);
339                         $b3 = ($ii & 63);
340                         $ii = ((($b1 * 64) + $b2) * 64) + $b3;
341                         $ent = sprintf ('&#%d;', $ii);
342                         $escaped_data .= $ent;
343                         $nn += 2;
344                 }
345                 //4 21 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
346                 else if ($ii>>3 == 30)
347                 {
348                         $b1 = ($ii & 7);
349                         $ii = ord($data[$nn+1]);
350                         $b2 = ($ii & 63);
351                         $ii = ord($data[$nn+2]);
352                         $b3 = ($ii & 63);
353                         $ii = ord($data[$nn+3]);
354                         $b4 = ($ii & 63);
355                         $ii = ((((($b1 * 64) + $b2) * 64) + $b3) * 64) + $b4;
356                         $ent = sprintf ('&#%d;', $ii);
357                         $escaped_data .= $ent;
358                         $nn += 3;
359                 }
360         }
361                                 break;
362 /*
363                         case 'CP1252_':
364                         case 'CP1252_US-ASCII':
365                                 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
366                                 $escaped_data = str_replace($GLOBALS['xml_iso88591_Entities']['in'], $GLOBALS['xml_iso88591_Entities']['out'], $escaped_data);
367                                 $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data);
368                                 break;
369                         case 'CP1252_UTF-8':
370                                 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
371                                 /// @todo we could use real UTF8 chars here instead of xml entities... (note that utf_8 encode all allone will NOT convert them)
372                                 $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data);
373                                 $escaped_data = utf8_encode($escaped_data);
374                                 break;
375                         case 'CP1252_ISO-8859-1':
376                                 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
377                                 // 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...
378                                 $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data);
379                                 break;
380 */
381                         default:
382                                 $escaped_data = '';
383                                 error_log("Converting from $src_encoding to $dest_encoding: not supported...");
384                 }
385                 return $escaped_data;
386         }
387
388         /// xml parser handler function for opening element tags
389         function xmlrpc_se($parser, $name, $attrs, $accept_single_vals=false)
390         {
391                 // if invalid xmlrpc already detected, skip all processing
392                 if ($GLOBALS['_xh']['isf'] < 2)
393                 {
394                         // check for correct element nesting
395                         // top level element can only be of 2 types
396                         /// @todo optimization creep: save this check into a bool variable, instead of using count() every time:
397                         ///       there is only a single top level element in xml anyway
398                         if (count($GLOBALS['_xh']['stack']) == 0)
399                         {
400                                 if ($name != 'METHODRESPONSE' && $name != 'METHODCALL' && (
401                                         $name != 'VALUE' && !$accept_single_vals))
402                                 {
403                                         $GLOBALS['_xh']['isf'] = 2;
404                                         $GLOBALS['_xh']['isf_reason'] = 'missing top level xmlrpc element';
405                                         return;
406                                 }
407                                 else
408                                 {
409                                         $GLOBALS['_xh']['rt'] = strtolower($name);
410                                         $GLOBALS['_xh']['rt'] = strtolower($name);
411                                 }
412                         }
413                         else
414                         {
415                                 // not top level element: see if parent is OK
416                                 $parent = end($GLOBALS['_xh']['stack']);
417                                 if (!array_key_exists($name, $GLOBALS['xmlrpc_valid_parents']) || !in_array($parent, $GLOBALS['xmlrpc_valid_parents'][$name]))
418                                 {
419                                         $GLOBALS['_xh']['isf'] = 2;
420                                         $GLOBALS['_xh']['isf_reason'] = "xmlrpc element $name cannot be child of $parent";
421                                         return;
422                                 }
423                         }
424
425                         switch($name)
426                         {
427                                 // optimize for speed switch cases: most common cases first
428                                 case 'VALUE':
429                                         /// @todo we could check for 2 VALUE elements inside a MEMBER or PARAM element
430                                         $GLOBALS['_xh']['vt']='value'; // indicator: no value found yet
431                                         $GLOBALS['_xh']['ac']='';
432                                         $GLOBALS['_xh']['lv']=1;
433                                         $GLOBALS['_xh']['php_class']=null;
434                                         break;
435                                 case 'I4':
436                                 case 'INT':
437                                 case 'STRING':
438                                 case 'BOOLEAN':
439                                 case 'DOUBLE':
440                                 case 'DATETIME.ISO8601':
441                                 case 'BASE64':
442                                         if ($GLOBALS['_xh']['vt']!='value')
443                                         {
444                                                 //two data elements inside a value: an error occurred!
445                                                 $GLOBALS['_xh']['isf'] = 2;
446                                                 $GLOBALS['_xh']['isf_reason'] = "$name element following a {$GLOBALS['_xh']['vt']} element inside a single value";
447                                                 return;
448                                         }
449                                         $GLOBALS['_xh']['ac']=''; // reset the accumulator
450                                         break;
451                                 case 'STRUCT':
452                                 case 'ARRAY':
453                                         if ($GLOBALS['_xh']['vt']!='value')
454                                         {
455                                                 //two data elements inside a value: an error occurred!
456                                                 $GLOBALS['_xh']['isf'] = 2;
457                                                 $GLOBALS['_xh']['isf_reason'] = "$name element following a {$GLOBALS['_xh']['vt']} element inside a single value";
458                                                 return;
459                                         }
460                                         // create an empty array to hold child values, and push it onto appropriate stack
461                                         $cur_val = array();
462                                         $cur_val['values'] = array();
463                                         $cur_val['type'] = $name;
464                                         // check for out-of-band information to rebuild php objs
465                                         // and in case it is found, save it
466                                         if (@isset($attrs['PHP_CLASS']))
467                                         {
468                                                 $cur_val['php_class'] = $attrs['PHP_CLASS'];
469                                         }
470                                         $GLOBALS['_xh']['valuestack'][] = $cur_val;
471                                         $GLOBALS['_xh']['vt']='data'; // be prepared for a data element next
472                                         break;
473                                 case 'DATA':
474                                         if ($GLOBALS['_xh']['vt']!='data')
475                                         {
476                                                 //two data elements inside a value: an error occurred!
477                                                 $GLOBALS['_xh']['isf'] = 2;
478                                                 $GLOBALS['_xh']['isf_reason'] = "found two data elements inside an array element";
479                                                 return;
480                                         }
481                                 case 'METHODCALL':
482                                 case 'METHODRESPONSE':
483                                 case 'PARAMS':
484                                         // valid elements that add little to processing
485                                         break;
486                                 case 'METHODNAME':
487                                 case 'NAME':
488                                         /// @todo we could check for 2 NAME elements inside a MEMBER element
489                                         $GLOBALS['_xh']['ac']='';
490                                         break;
491                                 case 'FAULT':
492                                         $GLOBALS['_xh']['isf']=1;
493                                         break;
494                                 case 'MEMBER':
495                                         $GLOBALS['_xh']['valuestack'][count($GLOBALS['_xh']['valuestack'])-1]['name']=''; // set member name to null, in case we do not find in the xml later on
496                                         //$GLOBALS['_xh']['ac']='';
497                                         // Drop trough intentionally
498                                 case 'PARAM':
499                                         // clear value type, so we can check later if no value has been passed for this param/member
500                                         $GLOBALS['_xh']['vt']=null;
501                                         break;
502                                 case 'NIL':
503                                 case 'EX:NIL':
504                                         if ($GLOBALS['xmlrpc_null_extension'])
505                                         {
506                                                 if ($GLOBALS['_xh']['vt']!='value')
507                                                 {
508                                                         //two data elements inside a value: an error occurred!
509                                                         $GLOBALS['_xh']['isf'] = 2;
510                                                         $GLOBALS['_xh']['isf_reason'] = "$name element following a {$GLOBALS['_xh']['vt']} element inside a single value";
511                                                         return;
512                                                 }
513                                                 $GLOBALS['_xh']['ac']=''; // reset the accumulator
514                                                 break;
515                                         }
516                                         // we do not support the <NIL/> extension, so
517                                         // drop through intentionally
518                                 default:
519                                         /// INVALID ELEMENT: RAISE ISF so that it is later recognized!!!
520                                         $GLOBALS['_xh']['isf'] = 2;
521                                         $GLOBALS['_xh']['isf_reason'] = "found not-xmlrpc xml element $name";
522                                         break;
523                         }
524
525                         // Save current element name to stack, to validate nesting
526                         $GLOBALS['_xh']['stack'][] = $name;
527
528                         /// @todo optimization creep: move this inside the big switch() above
529                         if($name!='VALUE')
530                         {
531                                 $GLOBALS['_xh']['lv']=0;
532                         }
533                 }
534         }
535
536         /// Used in decoding xml chunks that might represent single xmlrpc values
537         function xmlrpc_se_any($parser, $name, $attrs)
538         {
539                 xmlrpc_se($parser, $name, $attrs, true);
540         }
541
542         /// xml parser handler function for close element tags
543         function xmlrpc_ee($parser, $name, $rebuild_xmlrpcvals = true)
544         {
545                 if ($GLOBALS['_xh']['isf'] < 2)
546                 {
547                         // push this element name from stack
548                         // NB: if XML validates, correct opening/closing is guaranteed and
549                         // we do not have to check for $name == $curr_elem.
550                         // we also checked for proper nesting at start of elements...
551                         $curr_elem = array_pop($GLOBALS['_xh']['stack']);
552
553                         switch($name)
554                         {
555                                 case 'VALUE':
556                                         // This if() detects if no scalar was inside <VALUE></VALUE>
557                                         if ($GLOBALS['_xh']['vt']=='value')
558                                         {
559                                                 $GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac'];
560                                                 $GLOBALS['_xh']['vt']=$GLOBALS['xmlrpcString'];
561                                         }
562
563                                         if ($rebuild_xmlrpcvals)
564                                         {
565                                                 // build the xmlrpc val out of the data received, and substitute it
566                                                 $temp = new xmlrpcval($GLOBALS['_xh']['value'], $GLOBALS['_xh']['vt']);
567                                                 // in case we got info about underlying php class, save it
568                                                 // in the object we're rebuilding
569                                                 if (isset($GLOBALS['_xh']['php_class']))
570                                                         $temp->_php_class = $GLOBALS['_xh']['php_class'];
571                                                 // check if we are inside an array or struct:
572                                                 // if value just built is inside an array, let's move it into array on the stack
573                                                 $vscount = count($GLOBALS['_xh']['valuestack']);
574                                                 if ($vscount && $GLOBALS['_xh']['valuestack'][$vscount-1]['type']=='ARRAY')
575                                                 {
576                                                         $GLOBALS['_xh']['valuestack'][$vscount-1]['values'][] = $temp;
577                                                 }
578                                                 else
579                                                 {
580                                                         $GLOBALS['_xh']['value'] = $temp;
581                                                 }
582                                         }
583                                         else
584                                         {
585                                                 /// @todo this needs to treat correctly php-serialized objects,
586                                                 /// since std deserializing is done by php_xmlrpc_decode,
587                                                 /// which we will not be calling...
588                                                 if (isset($GLOBALS['_xh']['php_class']))
589                                                 {
590                                                 }
591
592                                                 // check if we are inside an array or struct:
593                                                 // if value just built is inside an array, let's move it into array on the stack
594                                                 $vscount = count($GLOBALS['_xh']['valuestack']);
595                                                 if ($vscount && $GLOBALS['_xh']['valuestack'][$vscount-1]['type']=='ARRAY')
596                                                 {
597                                                         $GLOBALS['_xh']['valuestack'][$vscount-1]['values'][] = $GLOBALS['_xh']['value'];
598                                                 }
599                                         }
600                                         break;
601                                 case 'BOOLEAN':
602                                 case 'I4':
603                                 case 'INT':
604                                 case 'STRING':
605                                 case 'DOUBLE':
606                                 case 'DATETIME.ISO8601':
607                                 case 'BASE64':
608                                         $GLOBALS['_xh']['vt']=strtolower($name);
609                                         /// @todo: optimization creep - remove the if/elseif cycle below
610                                         /// since the case() in which we are already did that
611                                         if ($name=='STRING')
612                                         {
613                                                 $GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac'];
614                                         }
615                                         elseif ($name=='DATETIME.ISO8601')
616                                         {
617                                                 if (!preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $GLOBALS['_xh']['ac']))
618                                                 {
619                                                         error_log('XML-RPC: invalid value received in DATETIME: '.$GLOBALS['_xh']['ac']);
620                                                 }
621                                                 $GLOBALS['_xh']['vt']=$GLOBALS['xmlrpcDateTime'];
622                                                 $GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac'];
623                                         }
624                                         elseif ($name=='BASE64')
625                                         {
626                                                 /// @todo check for failure of base64 decoding / catch warnings
627                                                 $GLOBALS['_xh']['value']=base64_decode($GLOBALS['_xh']['ac']);
628                                         }
629                                         elseif ($name=='BOOLEAN')
630                                         {
631                                                 // special case here: we translate boolean 1 or 0 into PHP
632                                                 // constants true or false.
633                                                 // Strings 'true' and 'false' are accepted, even though the
634                                                 // spec never mentions them (see eg. Blogger api docs)
635                                                 // NB: this simple checks helps a lot sanitizing input, ie no
636                                                 // security problems around here
637                                                 if ($GLOBALS['_xh']['ac']=='1' || strcasecmp($GLOBALS['_xh']['ac'], 'true') == 0)
638                                                 {
639                                                         $GLOBALS['_xh']['value']=true;
640                                                 }
641                                                 else
642                                                 {
643                                                         // log if receiveing something strange, even though we set the value to false anyway
644                                                         if ($GLOBALS['_xh']['ac']!='0' && strcasecmp($GLOBALS['_xh']['ac'], 'false') != 0)
645                                                                 error_log('XML-RPC: invalid value received in BOOLEAN: '.$GLOBALS['_xh']['ac']);
646                                                         $GLOBALS['_xh']['value']=false;
647                                                 }
648                                         }
649                                         elseif ($name=='DOUBLE')
650                                         {
651                                                 // we have a DOUBLE
652                                                 // we must check that only 0123456789-.<space> are characters here
653                                                 // NOTE: regexp could be much stricter than this...
654                                                 if (!preg_match('/^[+-eE0123456789 \t.]+$/', $GLOBALS['_xh']['ac']))
655                                                 {
656                                                         /// @todo: find a better way of throwing an error than this!
657                                                         error_log('XML-RPC: non numeric value received in DOUBLE: '.$GLOBALS['_xh']['ac']);
658                                                         $GLOBALS['_xh']['value']='ERROR_NON_NUMERIC_FOUND';
659                                                 }
660                                                 else
661                                                 {
662                                                         // it's ok, add it on
663                                                         $GLOBALS['_xh']['value']=(double)$GLOBALS['_xh']['ac'];
664                                                 }
665                                         }
666                                         else
667                                         {
668                                                 // we have an I4/INT
669                                                 // we must check that only 0123456789-<space> are characters here
670                                                 if (!preg_match('/^[+-]?[0123456789 \t]+$/', $GLOBALS['_xh']['ac']))
671                                                 {
672                                                         /// @todo find a better way of throwing an error than this!
673                                                         error_log('XML-RPC: non numeric value received in INT: '.$GLOBALS['_xh']['ac']);
674                                                         $GLOBALS['_xh']['value']='ERROR_NON_NUMERIC_FOUND';
675                                                 }
676                                                 else
677                                                 {
678                                                         // it's ok, add it on
679                                                         $GLOBALS['_xh']['value']=(int)$GLOBALS['_xh']['ac'];
680                                                 }
681                                         }
682                                         //$GLOBALS['_xh']['ac']=''; // is this necessary?
683                                         $GLOBALS['_xh']['lv']=3; // indicate we've found a value
684                                         break;
685                                 case 'NAME':
686                                         $GLOBALS['_xh']['valuestack'][count($GLOBALS['_xh']['valuestack'])-1]['name'] = $GLOBALS['_xh']['ac'];
687                                         break;
688                                 case 'MEMBER':
689                                         //$GLOBALS['_xh']['ac']=''; // is this necessary?
690                                         // add to array in the stack the last element built,
691                                         // unless no VALUE was found
692                                         if ($GLOBALS['_xh']['vt'])
693                                         {
694                                                 $vscount = count($GLOBALS['_xh']['valuestack']);
695                                                 $GLOBALS['_xh']['valuestack'][$vscount-1]['values'][$GLOBALS['_xh']['valuestack'][$vscount-1]['name']] = $GLOBALS['_xh']['value'];
696                                         } else
697                                                 error_log('XML-RPC: missing VALUE inside STRUCT in received xml');
698                                         break;
699                                 case 'DATA':
700                                         //$GLOBALS['_xh']['ac']=''; // is this necessary?
701                                         $GLOBALS['_xh']['vt']=null; // reset this to check for 2 data elements in a row - even if they're empty
702                                         break;
703                                 case 'STRUCT':
704                                 case 'ARRAY':
705                                         // fetch out of stack array of values, and promote it to current value
706                                         $curr_val = array_pop($GLOBALS['_xh']['valuestack']);
707                                         $GLOBALS['_xh']['value'] = $curr_val['values'];
708                                         $GLOBALS['_xh']['vt']=strtolower($name);
709                                         if (isset($curr_val['php_class']))
710                                         {
711                                                 $GLOBALS['_xh']['php_class'] = $curr_val['php_class'];
712                                         }
713                                         break;
714                                 case 'PARAM':
715                                         // add to array of params the current value,
716                                         // unless no VALUE was found
717                                         if ($GLOBALS['_xh']['vt'])
718                                         {
719                                                 $GLOBALS['_xh']['params'][]=$GLOBALS['_xh']['value'];
720                                                 $GLOBALS['_xh']['pt'][]=$GLOBALS['_xh']['vt'];
721                                         }
722                                         else
723                                                 error_log('XML-RPC: missing VALUE inside PARAM in received xml');
724                                         break;
725                                 case 'METHODNAME':
726                                         $GLOBALS['_xh']['method']=preg_replace('/^[\n\r\t ]+/', '', $GLOBALS['_xh']['ac']);
727                                         break;
728                                 case 'NIL':
729                                 case 'EX:NIL':
730                                         if ($GLOBALS['xmlrpc_null_extension'])
731                                         {
732                                                 $GLOBALS['_xh']['vt']='null';
733                                                 $GLOBALS['_xh']['value']=null;
734                                                 $GLOBALS['_xh']['lv']=3;
735                                                 break;
736                                         }
737                                         // drop through intentionally if nil extension not enabled
738                                 case 'PARAMS':
739                                 case 'FAULT':
740                                 case 'METHODCALL':
741                                 case 'METHORESPONSE':
742                                         break;
743                                 default:
744                                         // End of INVALID ELEMENT!
745                                         // shall we add an assert here for unreachable code???
746                                         break;
747                         }
748                 }
749         }
750
751         /// Used in decoding xmlrpc requests/responses without rebuilding xmlrpc values
752         function xmlrpc_ee_fast($parser, $name)
753         {
754                 xmlrpc_ee($parser, $name, false);
755         }
756
757         /// xml parser handler function for character data
758         function xmlrpc_cd($parser, $data)
759         {
760                 // skip processing if xml fault already detected
761                 if ($GLOBALS['_xh']['isf'] < 2)
762                 {
763                         // "lookforvalue==3" means that we've found an entire value
764                         // and should discard any further character data
765                         if($GLOBALS['_xh']['lv']!=3)
766                         {
767                                 // G. Giunta 2006-08-23: useless change of 'lv' from 1 to 2
768                                 //if($GLOBALS['_xh']['lv']==1)
769                                 //{
770                                         // if we've found text and we're just in a <value> then
771                                         // say we've found a value
772                                         //$GLOBALS['_xh']['lv']=2;
773                                 //}
774                                 // we always initialize the accumulator before starting parsing, anyway...
775                                 //if(!@isset($GLOBALS['_xh']['ac']))
776                                 //{
777                                 //      $GLOBALS['_xh']['ac'] = '';
778                                 //}
779                                 $GLOBALS['_xh']['ac'].=$data;
780                         }
781                 }
782         }
783
784         /// xml parser handler function for 'other stuff', ie. not char data or
785         /// element start/end tag. In fact it only gets called on unknown entities...
786         function xmlrpc_dh($parser, $data)
787         {
788                 // skip processing if xml fault already detected
789                 if ($GLOBALS['_xh']['isf'] < 2)
790                 {
791                         if(substr($data, 0, 1) == '&' && substr($data, -1, 1) == ';')
792                         {
793                                 // G. Giunta 2006-08-25: useless change of 'lv' from 1 to 2
794                                 //if($GLOBALS['_xh']['lv']==1)
795                                 //{
796                                 //      $GLOBALS['_xh']['lv']=2;
797                                 //}
798                                 $GLOBALS['_xh']['ac'].=$data;
799                         }
800                 }
801                 return true;
802         }
803
804         class xmlrpc_client
805         {
806                 var $path;
807                 var $server;
808                 var $port=0;
809                 var $method='http';
810                 var $errno;
811                 var $errstr;
812                 var $debug=0;
813                 var $username='';
814                 var $password='';
815                 var $authtype=1;
816                 var $cert='';
817                 var $certpass='';
818                 var $cacert='';
819                 var $cacertdir='';
820                 var $key='';
821                 var $keypass='';
822                 var $verifypeer=true;
823                 var $verifyhost=1;
824                 var $no_multicall=false;
825                 var $proxy='';
826                 var $proxyport=0;
827                 var $proxy_user='';
828                 var $proxy_pass='';
829                 var $proxy_authtype=1;
830                 var $cookies=array();
831                 var $extracurlopts=array();
832
833                 /**
834                 * List of http compression methods accepted by the client for responses.
835                 * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib
836                 *
837                 * NNB: you can set it to any non-empty array for HTTP11 and HTTPS, since
838                 * in those cases it will be up to CURL to decide the compression methods
839                 * it supports. You might check for the presence of 'zlib' in the output of
840                 * curl_version() to determine wheter compression is supported or not
841                 */
842                 var $accepted_compression = array();
843                 /**
844                 * Name of compression scheme to be used for sending requests.
845                 * Either null, gzip or deflate
846                 */
847                 var $request_compression = '';
848                 /**
849                 * CURL handle: used for keep-alive connections (PHP 4.3.8 up, see:
850                 * http://curl.haxx.se/docs/faq.html#7.3)
851                 */
852                 var $xmlrpc_curl_handle = null;
853                 /// Wheter to use persistent connections for http 1.1 and https
854                 var $keepalive = false;
855                 /// Charset encodings that can be decoded without problems by the client
856                 var $accepted_charset_encodings = array();
857                 /// Charset encoding to be used in serializing request. NULL = use ASCII
858                 var $request_charset_encoding = '';
859                 /**
860                 * Decides the content of xmlrpcresp objects returned by calls to send()
861                 * valid strings are 'xmlrpcvals', 'phpvals' or 'xml'
862                 */
863                 var $return_type = 'xmlrpcvals';
864                 /**
865                 * Sent to servers in http headers
866                 */
867                 var $user_agent;
868
869                 /**
870                 * @param string $path either the complete server URL or the PATH part of the xmlrc server URL, e.g. /xmlrpc/server.php
871                 * @param string $server the server name / ip address
872                 * @param integer $port the port the server is listening on, defaults to 80 or 443 depending on protocol used
873                 * @param string $method the http protocol variant: defaults to 'http', 'https' and 'http11' can be used if CURL is installed
874                 */
875                 function xmlrpc_client($path, $server='', $port='', $method='')
876                 {
877                         // allow user to specify all params in $path
878                         if($server == '' and $port == '' and $method == '')
879                         {
880                                 $parts = parse_url($path);
881                                 $server = $parts['host'];
882                                 $path = isset($parts['path']) ? $parts['path'] : '';
883                                 if(isset($parts['query']))
884                                 {
885                                         $path .= '?'.$parts['query'];
886                                 }
887                                 if(isset($parts['fragment']))
888                                 {
889                                         $path .= '#'.$parts['fragment'];
890                                 }
891                                 if(isset($parts['port']))
892                                 {
893                                         $port = $parts['port'];
894                                 }
895                                 if(isset($parts['scheme']))
896                                 {
897                                         $method = $parts['scheme'];
898                                 }
899                                 if(isset($parts['user']))
900                                 {
901                                         $this->username = $parts['user'];
902                                 }
903                                 if(isset($parts['pass']))
904                                 {
905                                         $this->password = $parts['pass'];
906                                 }
907                         }
908                         if($path == '' || $path[0] != '/')
909                         {
910                                 $this->path='/'.$path;
911                         }
912                         else
913                         {
914                                 $this->path=$path;
915                         }
916                         $this->server=$server;
917                         if($port != '')
918                         {
919                                 $this->port=$port;
920                         }
921                         if($method != '')
922                         {
923                                 $this->method=$method;
924                         }
925
926                         // if ZLIB is enabled, let the client by default accept compressed responses
927                         if(function_exists('gzinflate') || (
928                                 function_exists('curl_init') && (($info = curl_version()) &&
929                                 ((is_string($info) && strpos($info, 'zlib') !== null) || isset($info['libz_version'])))
930                         ))
931                         {
932                                 $this->accepted_compression = array('gzip', 'deflate');
933                         }
934
935                         // keepalives: enabled by default
936                         $this->keepalive = true;
937
938                         // by default the xml parser can support these 3 charset encodings
939                         $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII');
940
941                         // initialize user_agent string
942                         $this->user_agent = $GLOBALS['xmlrpcName'] . ' ' . $GLOBALS['xmlrpcVersion'];
943                 }
944
945                 /**
946                 * Enables/disables the echoing to screen of the xmlrpc responses received
947                 * @param integer $debug values 0, 1 and 2 are supported (2 = echo sent msg too, before received response)
948                 * @access public
949                 */
950                 function setDebug($in)
951                 {
952                         $this->debug=$in;
953                 }
954
955                 /**
956                 * Add some http BASIC AUTH credentials, used by the client to authenticate
957                 * @param string $u username
958                 * @param string $p password
959                 * @param integer $t auth type. See curl_setopt man page for supported auth types. Defaults to CURLAUTH_BASIC (basic auth)
960                 * @access public
961                 */
962                 function setCredentials($u, $p, $t=1)
963                 {
964                         $this->username=$u;
965                         $this->password=$p;
966                         $this->authtype=$t;
967                 }
968
969                 /**
970                 * Add a client-side https certificate
971                 * @param string $cert
972                 * @param string $certpass
973                 * @access public
974                 */
975                 function setCertificate($cert, $certpass)
976                 {
977                         $this->cert = $cert;
978                         $this->certpass = $certpass;
979                 }
980
981                 /**
982                 * Add a CA certificate to verify server with (see man page about
983                 * CURLOPT_CAINFO for more details
984                 * @param string $cacert certificate file name (or dir holding certificates)
985                 * @param bool $is_dir set to true to indicate cacert is a dir. defaults to false
986                 * @access public
987                 */
988                 function setCaCertificate($cacert, $is_dir=false)
989                 {
990                         if ($is_dir)
991                         {
992                                 $this->cacertdir = $cacert;
993                         }
994                         else
995                         {
996                                 $this->cacert = $cacert;
997                         }
998                 }
999
1000                 /**
1001                 * Set attributes for SSL communication: private SSL key
1002                 * NB: does not work in older php/curl installs
1003                 * Thanks to Daniel Convissor
1004                 * @param string $key The name of a file containing a private SSL key
1005                 * @param string $keypass The secret password needed to use the private SSL key
1006                 * @access public
1007                 */
1008                 function setKey($key, $keypass)
1009                 {
1010                         $this->key = $key;
1011                         $this->keypass = $keypass;
1012                 }
1013
1014                 /**
1015                 * Set attributes for SSL communication: verify server certificate
1016                 * @param bool $i enable/disable verification of peer certificate
1017                 * @access public
1018                 */
1019                 function setSSLVerifyPeer($i)
1020                 {
1021                         $this->verifypeer = $i;
1022                 }
1023
1024                 /**
1025                 * Set attributes for SSL communication: verify match of server cert w. hostname
1026                 * @param int $i
1027                 * @access public
1028                 */
1029                 function setSSLVerifyHost($i)
1030                 {
1031                         $this->verifyhost = $i;
1032                 }
1033
1034                 /**
1035                 * Set proxy info
1036                 * @param string $proxyhost
1037                 * @param string $proxyport Defaults to 8080 for HTTP and 443 for HTTPS
1038                 * @param string $proxyusername Leave blank if proxy has public access
1039                 * @param string $proxypassword Leave blank if proxy has public access
1040                 * @param int $proxyauthtype set to constant CURLAUTH_NTLM to use NTLM auth with proxy
1041                 * @access public
1042                 */
1043                 function setProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '', $proxyauthtype = 1)
1044                 {
1045                         $this->proxy = $proxyhost;
1046                         $this->proxyport = $proxyport;
1047                         $this->proxy_user = $proxyusername;
1048                         $this->proxy_pass = $proxypassword;
1049                         $this->proxy_authtype = $proxyauthtype;
1050                 }
1051
1052                 /**
1053                 * Enables/disables reception of compressed xmlrpc responses.
1054                 * Note that enabling reception of compressed responses merely adds some standard
1055                 * http headers to xmlrpc requests. It is up to the xmlrpc server to return
1056                 * compressed responses when receiving such requests.
1057                 * @param string $compmethod either 'gzip', 'deflate', 'any' or ''
1058                 * @access public
1059                 */
1060                 function setAcceptedCompression($compmethod)
1061                 {
1062                         if ($compmethod == 'any')
1063                                 $this->accepted_compression = array('gzip', 'deflate');
1064                         else
1065                                 $this->accepted_compression = array($compmethod);
1066                 }
1067
1068                 /**
1069                 * Enables/disables http compression of xmlrpc request.
1070                 * Take care when sending compressed requests: servers might not support them
1071                 * (and automatic fallback to uncompressed requests is not yet implemented)
1072                 * @param string $compmethod either 'gzip', 'deflate' or ''
1073                 * @access public
1074                 */
1075                 function setRequestCompression($compmethod)
1076                 {
1077                         $this->request_compression = $compmethod;
1078                 }
1079
1080                 /**
1081                 * Adds a cookie to list of cookies that will be sent to server.
1082                 * NB: setting any param but name and value will turn the cookie into a 'version 1' cookie:
1083                 * do not do it unless you know what you are doing
1084                 * @param string $name
1085                 * @param string $value
1086                 * @param string $path
1087                 * @param string $domain
1088                 * @param int $port
1089                 * @access public
1090                 *
1091                 * @todo check correctness of urlencoding cookie value (copied from php way of doing it...)
1092                 */
1093                 function setCookie($name, $value='', $path='', $domain='', $port=null)
1094                 {
1095                         $this->cookies[$name]['value'] = urlencode($value);
1096                         if ($path || $domain || $port)
1097                         {
1098                                 $this->cookies[$name]['path'] = $path;
1099                                 $this->cookies[$name]['domain'] = $domain;
1100                                 $this->cookies[$name]['port'] = $port;
1101                                 $this->cookies[$name]['version'] = 1;
1102                         }
1103                         else
1104                         {
1105                                 $this->cookies[$name]['version'] = 0;
1106                         }
1107                 }
1108
1109                 /**
1110                 * Directly set cURL options, for extra flexibility
1111                 * It allows eg. to bind client to a specific IP interface / address
1112                 * @param $options array
1113                 */
1114                 function SetCurlOptions( $options )
1115                 {
1116                         $this->extracurlopts = $options;
1117                 }
1118
1119                 /**
1120                 * Set user-agent string that will be used by this client instance
1121                 * in http headers sent to the server
1122                 */
1123                 function SetUserAgent( $agentstring )
1124                 {
1125                         $this->user_agent = $agentstring;
1126                 }
1127
1128                 /**
1129                 * Send an xmlrpc request
1130                 * @param mixed $msg The message object, or an array of messages for using multicall, or the complete xml representation of a request
1131                 * @param integer $timeout Connection timeout, in seconds, If unspecified, a platform specific timeout will apply
1132                 * @param string $method if left unspecified, the http protocol chosen during creation of the object will be used
1133                 * @return xmlrpcresp
1134                 * @access public
1135                 */
1136                 function& send($msg, $timeout=0, $method='')
1137                 {
1138                         // if user deos not specify http protocol, use native method of this client
1139                         // (i.e. method set during call to constructor)
1140                         if($method == '')
1141                         {
1142                                 $method = $this->method;
1143                         }
1144
1145                         if(is_array($msg))
1146                         {
1147                                 // $msg is an array of xmlrpcmsg's
1148                                 $r = $this->multicall($msg, $timeout, $method);
1149                                 return $r;
1150                         }
1151                         elseif(is_string($msg))
1152                         {
1153                                 $n = new xmlrpcmsg('');
1154                                 $n->payload = $msg;
1155                                 $msg = $n;
1156                         }
1157
1158                         // where msg is an xmlrpcmsg
1159                         $msg->debug=$this->debug;
1160
1161                         if($method == 'https')
1162                         {
1163                                 $r =& $this->sendPayloadHTTPS(
1164                                         $msg,
1165                                         $this->server,
1166                                         $this->port,
1167                                         $timeout,
1168                                         $this->username,
1169                                         $this->password,
1170                                         $this->authtype,
1171                                         $this->cert,
1172                                         $this->certpass,
1173                                         $this->cacert,
1174                                         $this->cacertdir,
1175                                         $this->proxy,
1176                                         $this->proxyport,
1177                                         $this->proxy_user,
1178                                         $this->proxy_pass,
1179                                         $this->proxy_authtype,
1180                                         $this->keepalive,
1181                                         $this->key,
1182                                         $this->keypass
1183                                 );
1184                         }
1185                         elseif($method == 'http11')
1186                         {
1187                                 $r =& $this->sendPayloadCURL(
1188                                         $msg,
1189                                         $this->server,
1190                                         $this->port,
1191                                         $timeout,
1192                                         $this->username,
1193                                         $this->password,
1194                                         $this->authtype,
1195                                         null,
1196                                         null,
1197                                         null,
1198                                         null,
1199                                         $this->proxy,
1200                                         $this->proxyport,
1201                                         $this->proxy_user,
1202                                         $this->proxy_pass,
1203                                         $this->proxy_authtype,
1204                                         'http',
1205                                         $this->keepalive
1206                                 );
1207                         }
1208                         else
1209                         {
1210                                 $r =& $this->sendPayloadHTTP10(
1211                                         $msg,
1212                                         $this->server,
1213                                         $this->port,
1214                                         $timeout,
1215                                         $this->username,
1216                                         $this->password,
1217                                         $this->authtype,
1218                                         $this->proxy,
1219                                         $this->proxyport,
1220                                         $this->proxy_user,
1221                                         $this->proxy_pass,
1222                                         $this->proxy_authtype
1223                                 );
1224                         }
1225
1226                         return $r;
1227                 }
1228
1229                 /**
1230                 * @access private
1231                 */
1232                 function &sendPayloadHTTP10($msg, $server, $port, $timeout=0,
1233                         $username='', $password='', $authtype=1, $proxyhost='',
1234                         $proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1)
1235                 {
1236                         if($port==0)
1237                         {
1238                                 $port=80;
1239                         }
1240
1241                         // Only create the payload if it was not created previously
1242                         if(empty($msg->payload))
1243                         {
1244                                 $msg->createPayload($this->request_charset_encoding);
1245                         }
1246
1247                         $payload = $msg->payload;
1248                         // Deflate request body and set appropriate request headers
1249                         if(function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate'))
1250                         {
1251                                 if($this->request_compression == 'gzip')
1252                                 {
1253                                         $a = @gzencode($payload);
1254                                         if($a)
1255                                         {
1256                                                 $payload = $a;
1257                                                 $encoding_hdr = "Content-Encoding: gzip\r\n";
1258                                         }
1259                                 }
1260                                 else
1261                                 {
1262                                         $a = @gzcompress($payload);
1263                                         if($a)
1264                                         {
1265                                                 $payload = $a;
1266                                                 $encoding_hdr = "Content-Encoding: deflate\r\n";
1267                                         }
1268                                 }
1269                         }
1270                         else
1271                         {
1272                                 $encoding_hdr = '';
1273                         }
1274
1275                         // thanks to Grant Rauscher <grant7@firstworld.net> for this
1276                         $credentials='';
1277                         if($username!='')
1278                         {
1279                                 $credentials='Authorization: Basic ' . base64_encode($username . ':' . $password) . "\r\n";
1280                                 if ($authtype != 1)
1281                                 {
1282                                         error_log('XML-RPC: '.__METHOD__.': warning. Only Basic auth is supported with HTTP 1.0');
1283                                 }
1284                         }
1285
1286                         $accepted_encoding = '';
1287                         if(is_array($this->accepted_compression) && count($this->accepted_compression))
1288                         {
1289                                 $accepted_encoding = 'Accept-Encoding: ' . implode(', ', $this->accepted_compression) . "\r\n";
1290                         }
1291
1292                         $proxy_credentials = '';
1293                         if($proxyhost)
1294                         {
1295                                 if($proxyport == 0)
1296                                 {
1297                                         $proxyport = 8080;
1298                                 }
1299                                 $connectserver = $proxyhost;
1300                                 $connectport = $proxyport;
1301                                 $uri = 'http://'.$server.':'.$port.$this->path;
1302                                 if($proxyusername != '')
1303                                 {
1304                                         if ($proxyauthtype != 1)
1305                                         {
1306                                                 error_log('XML-RPC: '.__METHOD__.': warning. Only Basic auth to proxy is supported with HTTP 1.0');
1307                                         }
1308                                         $proxy_credentials = 'Proxy-Authorization: Basic ' . base64_encode($proxyusername.':'.$proxypassword) . "\r\n";
1309                                 }
1310                         }
1311                         else
1312                         {
1313                                 $connectserver = $server;
1314                                 $connectport = $port;
1315                                 $uri = $this->path;
1316                         }
1317
1318                         // Cookie generation, as per rfc2965 (version 1 cookies) or
1319                         // netscape's rules (version 0 cookies)
1320                         $cookieheader='';
1321                         if (count($this->cookies))
1322                         {
1323                                 $version = '';
1324                                 foreach ($this->cookies as $name => $cookie)
1325                                 {
1326                                         if ($cookie['version'])
1327                                         {
1328                                                 $version = ' $Version="' . $cookie['version'] . '";';
1329                                                 $cookieheader .= ' ' . $name . '="' . $cookie['value'] . '";';
1330                                                 if ($cookie['path'])
1331                                                         $cookieheader .= ' $Path="' . $cookie['path'] . '";';
1332                                                 if ($cookie['domain'])
1333                                                         $cookieheader .= ' $Domain="' . $cookie['domain'] . '";';
1334                                                 if ($cookie['port'])
1335                                                         $cookieheader .= ' $Port="' . $cookie['port'] . '";';
1336                                         }
1337                                         else
1338                                         {
1339                                                 $cookieheader .= ' ' . $name . '=' . $cookie['value'] . ";";
1340                                         }
1341                                 }
1342                                 $cookieheader = 'Cookie:' . $version . substr($cookieheader, 0, -1) . "\r\n";
1343                         }
1344
1345                         $op= 'POST ' . $uri. " HTTP/1.0\r\n" .
1346                                 'User-Agent: ' . $this->user_agent . "\r\n" .
1347                                 'Host: '. $server . ':' . $port . "\r\n" .
1348                                 $credentials .
1349                                 $proxy_credentials .
1350                                 $accepted_encoding .
1351                                 $encoding_hdr .
1352                                 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings) . "\r\n" .
1353                                 $cookieheader .
1354                                 'Content-Type: ' . $msg->content_type . "\r\nContent-Length: " .
1355                                 strlen($payload) . "\r\n\r\n" .
1356                                 $payload;
1357
1358                         if($this->debug > 1)
1359                         {
1360                                 print "<PRE>\n---SENDING---\n" . htmlentities($op) . "\n---END---\n</PRE>";
1361                                 // let the client see this now in case http times out...
1362                                 flush();
1363                         }
1364
1365                         if($timeout>0)
1366                         {
1367                                 $fp=@fsockopen($connectserver, $connectport, $this->errno, $this->errstr, $timeout);
1368                         }
1369                         else
1370                         {
1371                                 $fp=@fsockopen($connectserver, $connectport, $this->errno, $this->errstr);
1372                         }
1373                         if($fp)
1374                         {
1375                                 if($timeout>0 && function_exists('stream_set_timeout'))
1376                                 {
1377                                         stream_set_timeout($fp, $timeout);
1378                                 }
1379                         }
1380                         else
1381                         {
1382                                 $this->errstr='Connect error: '.$this->errstr;
1383                                 $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $this->errstr . ' (' . $this->errno . ')');
1384                                 return $r;
1385                         }
1386
1387                         if(!fputs($fp, $op, strlen($op)))
1388                         {
1389                                 fclose($fp);
1390                                 $this->errstr='Write error';
1391                                 $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $this->errstr);
1392                                 return $r;
1393                         }
1394                         else
1395                         {
1396                                 // reset errno and errstr on succesful socket connection
1397                                 $this->errstr = '';
1398                         }
1399                         // G. Giunta 2005/10/24: close socket before parsing.
1400                         // should yeld slightly better execution times, and make easier recursive calls (e.g. to follow http redirects)
1401                         $ipd='';
1402                         do
1403                         {
1404                                 // shall we check for $data === FALSE?
1405                                 // as per the manual, it signals an error
1406                                 $ipd.=fread($fp, 32768);
1407                         } while(!feof($fp));
1408                         fclose($fp);
1409                         $r =& $msg->parseResponse($ipd, false, $this->return_type);
1410                         return $r;
1411
1412                 }
1413
1414                 /**
1415                 * @access private
1416                 */
1417                 function &sendPayloadHTTPS($msg, $server, $port, $timeout=0, $username='',
1418                         $password='', $authtype=1, $cert='',$certpass='', $cacert='', $cacertdir='',
1419                         $proxyhost='', $proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1,
1420                         $keepalive=false, $key='', $keypass='')
1421                 {
1422                         $r =& $this->sendPayloadCURL($msg, $server, $port, $timeout, $username,
1423                                 $password, $authtype, $cert, $certpass, $cacert, $cacertdir, $proxyhost, $proxyport,
1424                                 $proxyusername, $proxypassword, $proxyauthtype, 'https', $keepalive, $key, $keypass);
1425                         return $r;
1426                 }
1427
1428                 /**
1429                 * Contributed by Justin Miller <justin@voxel.net>
1430                 * Requires curl to be built into PHP
1431                 * NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers!
1432                 * @access private
1433                 */
1434                 function &sendPayloadCURL($msg, $server, $port, $timeout=0, $username='',
1435                         $password='', $authtype=1, $cert='', $certpass='', $cacert='', $cacertdir='',
1436                         $proxyhost='', $proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1, $method='https',
1437                         $keepalive=false, $key='', $keypass='')
1438                 {
1439                         if(!function_exists('curl_init'))
1440                         {
1441                                 $this->errstr='CURL unavailable on this install';
1442                                 $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_curl'], $GLOBALS['xmlrpcstr']['no_curl']);
1443                                 return $r;
1444                         }
1445                         if($method == 'https')
1446                         {
1447                                 if(($info = curl_version()) &&
1448                                         ((is_string($info) && strpos($info, 'OpenSSL') === null) || (is_array($info) && !isset($info['ssl_version']))))
1449                                 {
1450                                         $this->errstr='SSL unavailable on this install';
1451                                         $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_ssl'], $GLOBALS['xmlrpcstr']['no_ssl']);
1452                                         return $r;
1453                                 }
1454                         }
1455
1456                         if($port == 0)
1457                         {
1458                                 if($method == 'http')
1459                                 {
1460                                         $port = 80;
1461                                 }
1462                                 else
1463                                 {
1464                                         $port = 443;
1465                                 }
1466                         }
1467
1468                         // Only create the payload if it was not created previously
1469                         if(empty($msg->payload))
1470                         {
1471                                 $msg->createPayload($this->request_charset_encoding);
1472                         }
1473
1474                         // Deflate request body and set appropriate request headers
1475                         $payload = $msg->payload;
1476                         if(function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate'))
1477                         {
1478                                 if($this->request_compression == 'gzip')
1479                                 {
1480                                         $a = @gzencode($payload);
1481                                         if($a)
1482                                         {
1483                                                 $payload = $a;
1484                                                 $encoding_hdr = 'Content-Encoding: gzip';
1485                                         }
1486                                 }
1487                                 else
1488                                 {
1489                                         $a = @gzcompress($payload);
1490                                         if($a)
1491                                         {
1492                                                 $payload = $a;
1493                                                 $encoding_hdr = 'Content-Encoding: deflate';
1494                                         }
1495                                 }
1496                         }
1497                         else
1498                         {
1499                                 $encoding_hdr = '';
1500                         }
1501
1502                         if($this->debug > 1)
1503                         {
1504                                 print "<PRE>\n---SENDING---\n" . htmlentities($payload) . "\n---END---\n</PRE>";
1505                                 // let the client see this now in case http times out...
1506                                 flush();
1507                         }
1508
1509                         if(!$keepalive || !$this->xmlrpc_curl_handle)
1510                         {
1511                                 $curl = curl_init($method . '://' . $server . ':' . $port . $this->path);
1512                                 if($keepalive)
1513                                 {
1514                                         $this->xmlrpc_curl_handle = $curl;
1515                                 }
1516                         }
1517                         else
1518                         {
1519                                 $curl = $this->xmlrpc_curl_handle;
1520                         }
1521
1522                         // results into variable
1523                         curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
1524
1525                         if($this->debug)
1526                         {
1527                                 curl_setopt($curl, CURLOPT_VERBOSE, 1);
1528                         }
1529                         curl_setopt($curl, CURLOPT_USERAGENT, $this->user_agent);
1530                         // required for XMLRPC: post the data
1531                         curl_setopt($curl, CURLOPT_POST, 1);
1532                         // the data
1533                         curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
1534
1535                         // return the header too
1536                         curl_setopt($curl, CURLOPT_HEADER, 1);
1537
1538                         // will only work with PHP >= 5.0
1539                         // NB: if we set an empty string, CURL will add http header indicating
1540                         // ALL methods it is supporting. This is possibly a better option than
1541                         // letting the user tell what curl can / cannot do...
1542                         if(is_array($this->accepted_compression) && count($this->accepted_compression))
1543                         {
1544                                 //curl_setopt($curl, CURLOPT_ENCODING, implode(',', $this->accepted_compression));
1545                                 // empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1546                                 if (count($this->accepted_compression) == 1)
1547                                 {
1548                                         curl_setopt($curl, CURLOPT_ENCODING, $this->accepted_compression[0]);
1549                                 }
1550                                 else
1551                                         curl_setopt($curl, CURLOPT_ENCODING, '');
1552                         }
1553                         // extra headers
1554                         $headers = array('Content-Type: ' . $msg->content_type , 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings));
1555                         // if no keepalive is wanted, let the server know it in advance
1556                         if(!$keepalive)
1557                         {
1558                                 $headers[] = 'Connection: close';
1559                         }
1560                         // request compression header
1561                         if($encoding_hdr)
1562                         {
1563                                 $headers[] = $encoding_hdr;
1564                         }
1565
1566                         curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
1567                         // timeout is borked
1568                         if($timeout)
1569                         {
1570                                 curl_setopt($curl, CURLOPT_TIMEOUT, $timeout == 1 ? 1 : $timeout - 1);
1571                         }
1572
1573                         if($username && $password)
1574                         {
1575                                 curl_setopt($curl, CURLOPT_USERPWD, $username.':'.$password);
1576                                 if (defined('CURLOPT_HTTPAUTH'))
1577                                 {
1578                                         curl_setopt($curl, CURLOPT_HTTPAUTH, $authtype);
1579                                 }
1580                                 else if ($authtype != 1)
1581                                 {
1582                                         error_log('XML-RPC: '.__METHOD__.': warning. Only Basic auth is supported by the current PHP/curl install');
1583                                 }
1584                         }
1585
1586                         if($method == 'https')
1587                         {
1588                                 // set cert file
1589                                 if($cert)
1590                                 {
1591                                         curl_setopt($curl, CURLOPT_SSLCERT, $cert);
1592                                 }
1593                                 // set cert password
1594                                 if($certpass)
1595                                 {
1596                                         curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $certpass);
1597                                 }
1598                                 // whether to verify remote host's cert
1599                                 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verifypeer);
1600                                 // set ca certificates file/dir
1601                                 if($cacert)
1602                                 {
1603                                         curl_setopt($curl, CURLOPT_CAINFO, $cacert);
1604                                 }
1605                                 if($cacertdir)
1606                                 {
1607                                         curl_setopt($curl, CURLOPT_CAPATH, $cacertdir);
1608                                 }
1609                                 // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1610                                 if($key)
1611                                 {
1612                                         curl_setopt($curl, CURLOPT_SSLKEY, $key);
1613                                 }
1614                                 // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1615                                 if($keypass)
1616                                 {
1617                                         curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $keypass);
1618                                 }
1619                                 // whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that it matches the hostname used
1620                                 curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $this->verifyhost);
1621                         }
1622
1623                         // proxy info
1624                         if($proxyhost)
1625                         {
1626                                 if($proxyport == 0)
1627                                 {
1628                                         $proxyport = 8080; // NB: even for HTTPS, local connection is on port 8080
1629                                 }
1630                                 curl_setopt($curl, CURLOPT_PROXY, $proxyhost.':'.$proxyport);
1631                                 //curl_setopt($curl, CURLOPT_PROXYPORT,$proxyport);
1632                                 if($proxyusername)
1633                                 {
1634                                         curl_setopt($curl, CURLOPT_PROXYUSERPWD, $proxyusername.':'.$proxypassword);
1635                                         if (defined('CURLOPT_PROXYAUTH'))
1636                                         {
1637                                                 curl_setopt($curl, CURLOPT_PROXYAUTH, $proxyauthtype);
1638                                         }
1639                                         else if ($proxyauthtype != 1)
1640                                         {
1641                                                 error_log('XML-RPC: '.__METHOD__.': warning. Only Basic auth to proxy is supported by the current PHP/curl install');
1642                                         }
1643                                 }
1644                         }
1645
1646                         // NB: should we build cookie http headers by hand rather than let CURL do it?
1647                         // the following code does not honour 'expires', 'path' and 'domain' cookie attributes
1648                         // set to client obj the the user...
1649                         if (count($this->cookies))
1650                         {
1651                                 $cookieheader = '';
1652                                 foreach ($this->cookies as $name => $cookie)
1653                                 {
1654                                         $cookieheader .= $name . '=' . $cookie['value'] . '; ';
1655                                 }
1656                                 curl_setopt($curl, CURLOPT_COOKIE, substr($cookieheader, 0, -2));
1657                         }
1658
1659                         foreach ($this->extracurlopts as $opt => $val)
1660                         {
1661                                 curl_setopt($curl, $opt, $val);
1662                         }
1663
1664                         $result = curl_exec($curl);
1665
1666                         if ($this->debug > 1)
1667                         {
1668                                 print "<PRE>\n---CURL INFO---\n";
1669                                 foreach(curl_getinfo($curl) as $name => $val)
1670                                          print $name . ': ' . htmlentities($val). "\n";
1671                                 print "---END---\n</PRE>";
1672                         }
1673
1674                         if(!$result) /// @todo we should use a better check here - what if we get back '' or '0'?
1675                         {
1676                                 $this->errstr='no response';
1677                                 $resp=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['curl_fail'], $GLOBALS['xmlrpcstr']['curl_fail']. ': '. curl_error($curl));
1678                                 curl_close($curl);
1679                                 if($keepalive)
1680                                 {
1681                                         $this->xmlrpc_curl_handle = null;
1682                                 }
1683                         }
1684                         else
1685                         {
1686                                 if(!$keepalive)
1687                                 {
1688                                         curl_close($curl);
1689                                 }
1690                                 $resp =& $msg->parseResponse($result, true, $this->return_type);
1691                         }
1692                         return $resp;
1693                 }
1694
1695                 /**
1696                 * Send an array of request messages and return an array of responses.
1697                 * Unless $this->no_multicall has been set to true, it will try first
1698                 * to use one single xmlrpc call to server method system.multicall, and
1699                 * revert to sending many successive calls in case of failure.
1700                 * This failure is also stored in $this->no_multicall for subsequent calls.
1701                 * Unfortunately, there is no server error code universally used to denote
1702                 * the fact that multicall is unsupported, so there is no way to reliably
1703                 * distinguish between that and a temporary failure.
1704                 * If you are sure that server supports multicall and do not want to
1705                 * fallback to using many single calls, set the fourth parameter to FALSE.
1706                 *
1707                 * NB: trying to shoehorn extra functionality into existing syntax has resulted
1708                 * in pretty much convoluted code...
1709                 *
1710                 * @param array $msgs an array of xmlrpcmsg objects
1711                 * @param integer $timeout connection timeout (in seconds)
1712                 * @param string $method the http protocol variant to be used
1713                 * @param boolean fallback When true, upon receiveing an error during multicall, multiple single calls will be attempted
1714                 * @return array
1715                 * @access public
1716                 */
1717                 function multicall($msgs, $timeout=0, $method='', $fallback=true)
1718                 {
1719                         if ($method == '')
1720                         {
1721                                 $method = $this->method;
1722                         }
1723                         if(!$this->no_multicall)
1724                         {
1725                                 $results = $this->_try_multicall($msgs, $timeout, $method);
1726                                 if(is_array($results))
1727                                 {
1728                                         // System.multicall succeeded
1729                                         return $results;
1730                                 }
1731                                 else
1732                                 {
1733                                         // either system.multicall is unsupported by server,
1734                                         // or call failed for some other reason.
1735                                         if ($fallback)
1736                                         {
1737                                                 // Don't try it next time...
1738                                                 $this->no_multicall = true;
1739                                         }
1740                                         else
1741                                         {
1742                                                 if (is_a($results, 'xmlrpcresp'))
1743                                                 {
1744                                                         $result = $results;
1745                                                 }
1746                                                 else
1747                                                 {
1748                                                         $result = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['multicall_error'], $GLOBALS['xmlrpcstr']['multicall_error']);
1749                                                 }
1750                                         }
1751                                 }
1752                         }
1753                         else
1754                         {
1755                                 // override fallback, in case careless user tries to do two
1756                                 // opposite things at the same time
1757                                 $fallback = true;
1758                         }
1759
1760                         $results = array();
1761                         if ($fallback)
1762                         {
1763                                 // system.multicall is (probably) unsupported by server:
1764                                 // emulate multicall via multiple requests
1765                                 foreach($msgs as $msg)
1766                                 {
1767                                         $results[] =& $this->send($msg, $timeout, $method);
1768                                 }
1769                         }
1770                         else
1771                         {
1772                                 // user does NOT want to fallback on many single calls:
1773                                 // since we should always return an array of responses,
1774                                 // return an array with the same error repeated n times
1775                                 foreach($msgs as $msg)
1776                                 {
1777                                         $results[] = $result;
1778                                 }
1779                         }
1780                         return $results;
1781                 }
1782
1783                 /**
1784                 * Attempt to boxcar $msgs via system.multicall.
1785                 * Returns either an array of xmlrpcreponses, an xmlrpc error response
1786                 * or false (when received response does not respect valid multicall syntax)
1787                 * @access private
1788                 */
1789                 function _try_multicall($msgs, $timeout, $method)
1790                 {
1791                         // Construct multicall message
1792                         $calls = array();
1793                         foreach($msgs as $msg)
1794                         {
1795                                 $call['methodName'] = new xmlrpcval($msg->method(),'string');
1796                                 $numParams = $msg->getNumParams();
1797                                 $params = array();
1798                                 for($i = 0; $i < $numParams; $i++)
1799                                 {
1800                                         $params[$i] = $msg->getParam($i);
1801                                 }
1802                                 $call['params'] = new xmlrpcval($params, 'array');
1803                                 $calls[] = new xmlrpcval($call, 'struct');
1804                         }
1805                         $multicall = new xmlrpcmsg('system.multicall');
1806                         $multicall->addParam(new xmlrpcval($calls, 'array'));
1807
1808                         // Attempt RPC call
1809                         $result =& $this->send($multicall, $timeout, $method);
1810
1811                         if($result->faultCode() != 0)
1812                         {
1813                                 // call to system.multicall failed
1814                                 return $result;
1815                         }
1816
1817                         // Unpack responses.
1818                         $rets = $result->value();
1819
1820                         if ($this->return_type == 'xml')
1821                         {
1822                                         return $rets;
1823                         }
1824                         else if ($this->return_type == 'phpvals')
1825                         {
1826                                 ///@todo test this code branch...
1827                                 $rets = $result->value();
1828                                 if(!is_array($rets))
1829                                 {
1830                                         return false;           // bad return type from system.multicall
1831                                 }
1832                                 $numRets = count($rets);
1833                                 if($numRets != count($msgs))
1834                                 {
1835                                         return false;           // wrong number of return values.
1836                                 }
1837
1838                                 $response = array();
1839                                 for($i = 0; $i < $numRets; $i++)
1840                                 {
1841                                         $val = $rets[$i];
1842                                         if (!is_array($val)) {
1843                                                 return false;
1844                                         }
1845                                         switch(count($val))
1846                                         {
1847                                                 case 1:
1848                                                         if(!isset($val[0]))
1849                                                         {
1850                                                                 return false;           // Bad value
1851                                                         }
1852                                                         // Normal return value
1853                                                         $response[$i] = new xmlrpcresp($val[0], 0, '', 'phpvals');
1854                                                         break;
1855                                                 case 2:
1856                                                         ///     @todo remove usage of @: it is apparently quite slow
1857                                                         $code = @$val['faultCode'];
1858                                                         if(!is_int($code))
1859                                                         {
1860                                                                 return false;
1861                                                         }
1862                                                         $str = @$val['faultString'];
1863                                                         if(!is_string($str))
1864                                                         {
1865                                                                 return false;
1866                                                         }
1867                                                         $response[$i] = new xmlrpcresp(0, $code, $str);
1868                                                         break;
1869                                                 default:
1870                                                         return false;
1871                                         }
1872                                 }
1873                                 return $response;
1874                         }
1875                         else // return type == 'xmlrpcvals'
1876                         {
1877                                 $rets = $result->value();
1878                                 if($rets->kindOf() != 'array')
1879                                 {
1880                                         return false;           // bad return type from system.multicall
1881                                 }
1882                                 $numRets = $rets->arraysize();
1883                                 if($numRets != count($msgs))
1884                                 {
1885                                         return false;           // wrong number of return values.
1886                                 }
1887
1888                                 $response = array();
1889                                 for($i = 0; $i < $numRets; $i++)
1890                                 {
1891                                         $val = $rets->arraymem($i);
1892                                         switch($val->kindOf())
1893                                         {
1894                                                 case 'array':
1895                                                         if($val->arraysize() != 1)
1896                                                         {
1897                                                                 return false;           // Bad value
1898                                                         }
1899                                                         // Normal return value
1900                                                         $response[$i] = new xmlrpcresp($val->arraymem(0));
1901                                                         break;
1902                                                 case 'struct':
1903                                                         $code = $val->structmem('faultCode');
1904                                                         if($code->kindOf() != 'scalar' || $code->scalartyp() != 'int')
1905                                                         {
1906                                                                 return false;
1907                                                         }
1908                                                         $str = $val->structmem('faultString');
1909                                                         if($str->kindOf() != 'scalar' || $str->scalartyp() != 'string')
1910                                                         {
1911                                                                 return false;
1912                                                         }
1913                                                         $response[$i] = new xmlrpcresp(0, $code->scalarval(), $str->scalarval());
1914                                                         break;
1915                                                 default:
1916                                                         return false;
1917                                         }
1918                                 }
1919                                 return $response;
1920                         }
1921                 }
1922         } // end class xmlrpc_client
1923
1924         class xmlrpcresp
1925         {
1926                 var $val = 0;
1927                 var $valtyp;
1928                 var $errno = 0;
1929                 var $errstr = '';
1930                 var $payload;
1931                 var $hdrs = array();
1932                 var $_cookies = array();
1933                 var $content_type = 'text/xml';
1934                 var $raw_data = '';
1935
1936                 /**
1937                 * @param mixed $val either an xmlrpcval obj, a php value or the xml serialization of an xmlrpcval (a string)
1938                 * @param integer $fcode set it to anything but 0 to create an error response
1939                 * @param string $fstr the error string, in case of an error response
1940                 * @param string $valtyp either 'xmlrpcvals', 'phpvals' or 'xml'
1941                 *
1942                 * @todo add check that $val / $fcode / $fstr is of correct type???
1943                 * NB: as of now we do not do it, since it might be either an xmlrpcval or a plain
1944                 * php val, or a complete xml chunk, depending on usage of xmlrpc_client::send() inside which creator is called...
1945                 */
1946                 function xmlrpcresp($val, $fcode = 0, $fstr = '', $valtyp='')
1947                 {
1948                         if($fcode != 0)
1949                         {
1950                                 // error response
1951                                 $this->errno = $fcode;
1952                                 $this->errstr = $fstr;
1953                                 //$this->errstr = htmlspecialchars($fstr); // XXX: encoding probably shouldn't be done here; fix later.
1954                         }
1955                         else
1956                         {
1957                                 // successful response
1958                                 $this->val = $val;
1959                                 if ($valtyp == '')
1960                                 {
1961                                         // user did not declare type of response value: try to guess it
1962                                         if (is_object($this->val) && is_a($this->val, 'xmlrpcval'))
1963                                         {
1964                                                 $this->valtyp = 'xmlrpcvals';
1965                                         }
1966                                         else if (is_string($this->val))
1967                                         {
1968                                                 $this->valtyp = 'xml';
1969
1970                                         }
1971                                         else
1972                                         {
1973                                                 $this->valtyp = 'phpvals';
1974                                         }
1975                                 }
1976                                 else
1977                                 {
1978                                         // user declares type of resp value: believe him
1979                                         $this->valtyp = $valtyp;
1980                                 }
1981                         }
1982                 }
1983
1984                 /**
1985                 * Returns the error code of the response.
1986                 * @return integer the error code of this response (0 for not-error responses)
1987                 * @access public
1988                 */
1989                 function faultCode()
1990                 {
1991                         return $this->errno;
1992                 }
1993
1994                 /**
1995                 * Returns the error code of the response.
1996                 * @return string the error string of this response ('' for not-error responses)
1997                 * @access public
1998                 */
1999                 function faultString()
2000                 {
2001                         return $this->errstr;
2002                 }
2003
2004                 /**
2005                 * Returns the value received by the server.
2006                 * @return mixed the xmlrpcval object returned by the server. Might be an xml string or php value if the response has been created by specially configured xmlrpc_client objects
2007                 * @access public
2008                 */
2009                 function value()
2010                 {
2011                         return $this->val;
2012                 }
2013
2014                 /**
2015                 * Returns an array with the cookies received from the server.
2016                 * Array has the form: $cookiename => array ('value' => $val, $attr1 => $val1, $attr2 = $val2, ...)
2017                 * with attributes being e.g. 'expires', 'path', domain'.
2018                 * NB: cookies sent as 'expired' by the server (i.e. with an expiry date in the past)
2019                 * are still present in the array. It is up to the user-defined code to decide
2020                 * how to use the received cookies, and wheter they have to be sent back with the next
2021                 * request to the server (using xmlrpc_client::setCookie) or not
2022                 * @return array array of cookies received from the server
2023                 * @access public
2024                 */
2025                 function cookies()
2026                 {
2027                         return $this->_cookies;
2028                 }
2029
2030                 /**
2031                 * Returns xml representation of the response. XML prologue not included
2032                 * @param string $charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed
2033                 * @return string the xml representation of the response
2034                 * @access public
2035                 */
2036                 function serialize($charset_encoding='')
2037                 {
2038                         if ($charset_encoding != '')
2039                                 $this->content_type = 'text/xml; charset=' . $charset_encoding;
2040                         else
2041                                 $this->content_type = 'text/xml';
2042                         $result = "<methodResponse>\n";
2043                         if($this->errno)
2044                         {
2045                                 // G. Giunta 2005/2/13: let non-ASCII response messages be tolerated by clients
2046                                 // by xml-encoding non ascii chars
2047                                 $result .= "<fault>\n" .
2048 "<value>\n<struct><member><name>faultCode</name>\n<value><int>" . $this->errno .
2049 "</int></value>\n</member>\n<member>\n<name>faultString</name>\n<value><string>" .
2050 xmlrpc_encode_entitites($this->errstr, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding) . "</string></value>\n</member>\n" .
2051 "</struct>\n</value>\n</fault>";
2052                         }
2053                         else
2054                         {
2055                                 if(!is_object($this->val) || !is_a($this->val, 'xmlrpcval'))
2056                                 {
2057                                         if (is_string($this->val) && $this->valtyp == 'xml')
2058                                         {
2059                                                 $result .= "<params>\n<param>\n" .
2060                                                         $this->val .
2061                                                         "</param>\n</params>";
2062                                         }
2063                                         else
2064                                         {
2065                                                 /// @todo try to build something serializable?
2066                                                 die('cannot serialize xmlrpcresp objects whose content is native php values');
2067                                         }
2068                                 }
2069                                 else
2070                                 {
2071                                         $result .= "<params>\n<param>\n" .
2072                                                 $this->val->serialize($charset_encoding) .
2073                                                 "</param>\n</params>";
2074                                 }
2075                         }
2076                         $result .= "\n</methodResponse>";
2077                         $this->payload = $result;
2078                         return $result;
2079                 }
2080         }
2081
2082         class xmlrpcmsg
2083         {
2084                 var $payload;
2085                 var $methodname;
2086                 var $params=array();
2087                 var $debug=0;
2088                 var $content_type = 'text/xml';
2089
2090                 /**
2091                 * @param string $meth the name of the method to invoke
2092                 * @param array $pars array of parameters to be paased to the method (xmlrpcval objects)
2093                 */
2094                 function xmlrpcmsg($meth, $pars=0)
2095                 {
2096                         $this->methodname=$meth;
2097                         if(is_array($pars) && count($pars)>0)
2098                         {
2099                                 for($i=0; $i<count($pars); $i++)
2100                                 {
2101                                         $this->addParam($pars[$i]);
2102                                 }
2103                         }
2104                 }
2105
2106                 /**
2107                 * @access private
2108                 */
2109                 function xml_header($charset_encoding='')
2110                 {
2111                         if ($charset_encoding != '')
2112                         {
2113                                 return "<?xml version=\"1.0\" encoding=\"$charset_encoding\" ?" . ">\n<methodCall>\n";
2114                         }
2115                         else
2116                         {
2117                                 return "<?xml version=\"1.0\"?" . ">\n<methodCall>\n";
2118                         }
2119                 }
2120
2121                 /**
2122                 * @access private
2123                 */
2124                 function xml_footer()
2125                 {
2126                         return '</methodCall>';
2127                 }
2128
2129                 /**
2130                 * @access private
2131                 */
2132                 function kindOf()
2133                 {
2134                         return 'msg';
2135                 }
2136
2137                 /**
2138                 * @access private
2139                 */
2140                 function createPayload($charset_encoding='')
2141                 {
2142                         if ($charset_encoding != '')
2143                                 $this->content_type = 'text/xml; charset=' . $charset_encoding;
2144                         else
2145                                 $this->content_type = 'text/xml';
2146                         $this->payload=$this->xml_header($charset_encoding);
2147                         $this->payload.='<methodName>' . $this->methodname . "</methodName>\n";
2148                         $this->payload.="<params>\n";
2149                         for($i=0; $i<count($this->params); $i++)
2150                         {
2151                                 $p=$this->params[$i];
2152                                 $this->payload.="<param>\n" . $p->serialize($charset_encoding) .
2153                                 "</param>\n";
2154                         }
2155                         $this->payload.="</params>\n";
2156                         $this->payload.=$this->xml_footer();
2157                 }
2158
2159                 /**
2160                 * Gets/sets the xmlrpc method to be invoked
2161                 * @param string $meth the method to be set (leave empty not to set it)
2162                 * @return string the method that will be invoked
2163                 * @access public
2164                 */
2165                 function method($meth='')
2166                 {
2167                         if($meth!='')
2168                         {
2169                                 $this->methodname=$meth;
2170                         }
2171                         return $this->methodname;
2172                 }
2173
2174                 /**
2175                 * Returns xml representation of the message. XML prologue included
2176                 * @return string the xml representation of the message, xml prologue included
2177                 * @access public
2178                 */
2179                 function serialize($charset_encoding='')
2180                 {
2181                         $this->createPayload($charset_encoding);
2182                         return $this->payload;
2183                 }
2184
2185                 /**
2186                 * Add a parameter to the list of parameters to be used upon method invocation
2187                 * @param xmlrpcval $par
2188                 * @return boolean false on failure
2189                 * @access public
2190                 */
2191                 function addParam($par)
2192                 {
2193                         // add check: do not add to self params which are not xmlrpcvals
2194                         if(is_object($par) && is_a($par, 'xmlrpcval'))
2195                         {
2196                                 $this->params[]=$par;
2197                                 return true;
2198                         }
2199                         else
2200                         {
2201                                 return false;
2202                         }
2203                 }
2204
2205                 /**
2206                 * Returns the nth parameter in the message. The index zero-based.
2207                 * @param integer $i the index of the parameter to fetch (zero based)
2208                 * @return xmlrpcval the i-th parameter
2209                 * @access public
2210                 */
2211                 function getParam($i) { return $this->params[$i]; }
2212
2213                 /**
2214                 * Returns the number of parameters in the messge.
2215                 * @return integer the number of parameters currently set
2216                 * @access public
2217                 */
2218                 function getNumParams() { return count($this->params); }
2219
2220                 /**
2221                 * Given an open file handle, read all data available and parse it as axmlrpc response.
2222                 * NB: the file handle is not closed by this function.
2223                 * NNB: might have trouble in rare cases to work on network streams, as we
2224                 *      check for a read of 0 bytes instead of feof($fp).
2225                 *      But since checking for feof(null) returns false, we would risk an
2226                 *      infinite loop in that case, because we cannot trust the caller
2227                 *      to give us a valid pointer to an open file...
2228                 * @access public
2229                 * @return xmlrpcresp
2230                 * @todo add 2nd & 3rd param to be passed to ParseResponse() ???
2231                 */
2232                 function &parseResponseFile($fp)
2233                 {
2234                         $ipd='';
2235                         while($data=fread($fp, 32768))
2236                         {
2237                                 $ipd.=$data;
2238                         }
2239                         //fclose($fp);
2240                         $r =& $this->parseResponse($ipd);
2241                         return $r;
2242                 }
2243
2244                 /**
2245                 * Parses HTTP headers and separates them from data.
2246                 * @access private
2247                 */
2248                 function &parseResponseHeaders(&$data, $headers_processed=false)
2249                 {
2250                                 // Support "web-proxy-tunelling" connections for https through proxies
2251                                 if(preg_match('/^HTTP\/1\.[0-1] 200 Connection established/', $data))
2252                                 {
2253                                         // Look for CR/LF or simple LF as line separator,
2254                                         // (even though it is not valid http)
2255                                         $pos = strpos($data,"\r\n\r\n");
2256                                         if($pos || is_int($pos))
2257                                         {
2258                                                 $bd = $pos+4;
2259                                         }
2260                                         else
2261                                         {
2262                                                 $pos = strpos($data,"\n\n");
2263                                                 if($pos || is_int($pos))
2264                                                 {
2265                                                         $bd = $pos+2;
2266                                                 }
2267                                                 else
2268                                                 {
2269                                                         // No separation between response headers and body: fault?
2270                                                         $bd = 0;
2271                                                 }
2272                                         }
2273                                         if ($bd)
2274                                         {
2275                                                 // this filters out all http headers from proxy.
2276                                                 // maybe we could take them into account, too?
2277                                                 $data = substr($data, $bd);
2278                                         }
2279                                         else
2280                                         {
2281                                                 error_log('XML-RPC: '.__METHOD__.': HTTPS via proxy error, tunnel connection possibly failed');
2282                                                 $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $GLOBALS['xmlrpcstr']['http_error']. ' (HTTPS via proxy error, tunnel connection possibly failed)');
2283                                                 return $r;
2284                                         }
2285                                 }
2286
2287                                 // Strip HTTP 1.1 100 Continue header if present
2288                                 while(preg_match('/^HTTP\/1\.1 1[0-9]{2} /', $data))
2289                                 {
2290                                         $pos = strpos($data, 'HTTP', 12);
2291                                         // server sent a Continue header without any (valid) content following...
2292                                         // give the client a chance to know it
2293                                         if(!$pos && !is_int($pos)) // works fine in php 3, 4 and 5
2294                                         {
2295                                                 break;
2296                                         }
2297                                         $data = substr($data, $pos);
2298                                 }
2299                                 if(!preg_match('/^HTTP\/[0-9.]+ 200 /', $data))
2300                                 {
2301                                         $errstr= substr($data, 0, strpos($data, "\n")-1);
2302                                         error_log('XML-RPC: '.__METHOD__.': HTTP error, got response: ' .$errstr);
2303                                         $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $GLOBALS['xmlrpcstr']['http_error']. ' (' . $errstr . ')');
2304                                         return $r;
2305                                 }
2306
2307                                 $GLOBALS['_xh']['headers'] = array();
2308                                 $GLOBALS['_xh']['cookies'] = array();
2309
2310                                 // be tolerant to usage of \n instead of \r\n to separate headers and data
2311                                 // (even though it is not valid http)
2312                                 $pos = strpos($data,"\r\n\r\n");
2313                                 if($pos || is_int($pos))
2314                                 {
2315                                         $bd = $pos+4;
2316                                 }
2317                                 else
2318                                 {
2319                                         $pos = strpos($data,"\n\n");
2320                                         if($pos || is_int($pos))
2321                                         {
2322                                                 $bd = $pos+2;
2323                                         }
2324                                         else
2325                                         {
2326                                                 // No separation between response headers and body: fault?
2327                                                 // we could take some action here instead of going on...
2328                                                 $bd = 0;
2329                                         }
2330                                 }
2331                                 // be tolerant to line endings, and extra empty lines
2332                                 $ar = preg_split("/\r?\n/", trim(substr($data, 0, $pos)));
2333                                 while(list(,$line) = @each($ar))
2334                                 {
2335                                         // take care of multi-line headers and cookies
2336                                         $arr = explode(':',$line,2);
2337                                         if(count($arr) > 1)
2338                                         {
2339                                                 $header_name = strtolower(trim($arr[0]));
2340                                                 /// @todo some other headers (the ones that allow a CSV list of values)
2341                                                 /// do allow many values to be passed using multiple header lines.
2342                                                 /// We should add content to $GLOBALS['_xh']['headers'][$header_name]
2343                                                 /// instead of replacing it for those...
2344                                                 if ($header_name == 'set-cookie' || $header_name == 'set-cookie2')
2345                                                 {
2346                                                         if ($header_name == 'set-cookie2')
2347                                                         {
2348                                                                 // version 2 cookies:
2349                                                                 // there could be many cookies on one line, comma separated
2350                                                                 $cookies = explode(',', $arr[1]);
2351                                                         }
2352                                                         else
2353                                                         {
2354                                                                 $cookies = array($arr[1]);
2355                                                         }
2356                                                         foreach ($cookies as $cookie)
2357                                                         {
2358                                                                 // glue together all received cookies, using a comma to separate them
2359                                                                 // (same as php does with getallheaders())
2360                                                                 if (isset($GLOBALS['_xh']['headers'][$header_name]))
2361                                                                         $GLOBALS['_xh']['headers'][$header_name] .= ', ' . trim($cookie);
2362                                                                 else
2363                                                                         $GLOBALS['_xh']['headers'][$header_name] = trim($cookie);
2364                                                                 // parse cookie attributes, in case user wants to correctly honour them
2365                                                                 // feature creep: only allow rfc-compliant cookie attributes?
2366                                                                 // @todo support for server sending multiple time cookie with same name, but using different PATHs
2367                                                                 $cookie = explode(';', $cookie);
2368                                                                 foreach ($cookie as $pos => $val)
2369                                                                 {
2370                                                                         $val = explode('=', $val, 2);
2371                                                                         $tag = trim($val[0]);
2372                                                                         $val = trim(@$val[1]);
2373                                                                         /// @todo with version 1 cookies, we should strip leading and trailing " chars
2374                                                                         if ($pos == 0)
2375                                                                         {
2376                                                                                 $cookiename = $tag;
2377                                                                                 $GLOBALS['_xh']['cookies'][$tag] = array();
2378                                                                                 $GLOBALS['_xh']['cookies'][$cookiename]['value'] = urldecode($val);
2379                                                                         }
2380                                                                         else
2381                                                                         {
2382                                                                                 if ($tag != 'value')
2383                                                                                 {
2384                                                                                   $GLOBALS['_xh']['cookies'][$cookiename][$tag] = $val;
2385                                                                                 }
2386                                                                         }
2387                                                                 }
2388                                                         }
2389                                                 }
2390                                                 else
2391                                                 {
2392                                                         $GLOBALS['_xh']['headers'][$header_name] = trim($arr[1]);
2393                                                 }
2394                                         }
2395                                         elseif(isset($header_name))
2396                                         {
2397                                                 ///     @todo version1 cookies might span multiple lines, thus breaking the parsing above
2398                                                 $GLOBALS['_xh']['headers'][$header_name] .= ' ' . trim($line);
2399                                         }
2400                                 }
2401
2402                                 $data = substr($data, $bd);
2403
2404                                 if($this->debug && count($GLOBALS['_xh']['headers']))
2405                                 {
2406                                         print '<PRE>';
2407                                         foreach($GLOBALS['_xh']['headers'] as $header => $value)
2408                                         {
2409                                                 print htmlentities("HEADER: $header: $value\n");
2410                                         }
2411                                         foreach($GLOBALS['_xh']['cookies'] as $header => $value)
2412                                         {
2413                                                 print htmlentities("COOKIE: $header={$value['value']}\n");
2414                                         }
2415                                         print "</PRE>\n";
2416                                 }
2417
2418                                 // if CURL was used for the call, http headers have been processed,
2419                                 // and dechunking + reinflating have been carried out
2420                                 if(!$headers_processed)
2421                                 {
2422                                         // Decode chunked encoding sent by http 1.1 servers
2423                                         if(isset($GLOBALS['_xh']['headers']['transfer-encoding']) && $GLOBALS['_xh']['headers']['transfer-encoding'] == 'chunked')
2424                                         {
2425                                                 if(!$data = decode_chunked($data))
2426                                                 {
2427                                                         error_log('XML-RPC: '.__METHOD__.': errors occurred when trying to rebuild the chunked data received from server');
2428                                                         $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['dechunk_fail'], $GLOBALS['xmlrpcstr']['dechunk_fail']);
2429                                                         return $r;
2430                                                 }
2431                                         }
2432
2433                                         // Decode gzip-compressed stuff
2434                                         // code shamelessly inspired from nusoap library by Dietrich Ayala
2435                                         if(isset($GLOBALS['_xh']['headers']['content-encoding']))
2436                                         {
2437                                                 $GLOBALS['_xh']['headers']['content-encoding'] = str_replace('x-', '', $GLOBALS['_xh']['headers']['content-encoding']);
2438                                                 if($GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' || $GLOBALS['_xh']['headers']['content-encoding'] == 'gzip')
2439                                                 {
2440                                                         // if decoding works, use it. else assume data wasn't gzencoded
2441                                                         if(function_exists('gzinflate'))
2442                                                         {
2443                                                                 if($GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' && $degzdata = @gzuncompress($data))
2444                                                                 {
2445                                                                         $data = $degzdata;
2446                                                                         if($this->debug)
2447                                                                         print "<PRE>---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---</PRE>";
2448                                                                 }
2449                                                                 elseif($GLOBALS['_xh']['headers']['content-encoding'] == 'gzip' && $degzdata = @gzinflate(substr($data, 10)))
2450                                                                 {
2451                                                                         $data = $degzdata;
2452                                                                         if($this->debug)
2453                                                                         print "<PRE>---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---</PRE>";
2454                                                                 }
2455                                                                 else
2456                                                                 {
2457                                                                         error_log('XML-RPC: '.__METHOD__.': errors occurred when trying to decode the deflated data received from server');
2458                                                                         $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['decompress_fail'], $GLOBALS['xmlrpcstr']['decompress_fail']);
2459                                                                         return $r;
2460                                                                 }
2461                                                         }
2462                                                         else
2463                                                         {
2464                                                                 error_log('XML-RPC: '.__METHOD__.': the server sent deflated data. Your php install must have the Zlib extension compiled in to support this.');
2465                                                                 $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['cannot_decompress'], $GLOBALS['xmlrpcstr']['cannot_decompress']);
2466                                                                 return $r;
2467                                                         }
2468                                                 }
2469                                         }
2470                                 } // end of 'if needed, de-chunk, re-inflate response'
2471
2472                                 // real stupid hack to avoid PHP complaining about returning NULL by ref
2473                                 $r = null;
2474                                 $r =& $r;
2475                                 return $r;
2476                 }
2477
2478                 /**
2479                 * Parse the xmlrpc response contained in the string $data and return an xmlrpcresp object.
2480                 * @param string $data the xmlrpc response, eventually including http headers
2481                 * @param bool $headers_processed when true prevents parsing HTTP headers for interpretation of content-encoding and consequent decoding
2482                 * @param string $return_type decides return type, i.e. content of response->value(). Either 'xmlrpcvals', 'xml' or 'phpvals'
2483                 * @return xmlrpcresp
2484                 * @access public
2485                 */
2486                 function &parseResponse($data='', $headers_processed=false, $return_type='xmlrpcvals')
2487                 {
2488                         if($this->debug)
2489                         {
2490                                 //by maHo, replaced htmlspecialchars with htmlentities
2491                                 print "<PRE>---GOT---\n" . htmlentities($data) . "\n---END---\n</PRE>";
2492                         }
2493
2494                         if($data == '')
2495                         {
2496                                 error_log('XML-RPC: '.__METHOD__.': no response received from server.');
2497                                 $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_data'], $GLOBALS['xmlrpcstr']['no_data']);
2498                                 return $r;
2499                         }
2500
2501                         $GLOBALS['_xh']=array();
2502
2503                         $raw_data = $data;
2504                         // parse the HTTP headers of the response, if present, and separate them from data
2505                         if(substr($data, 0, 4) == 'HTTP')
2506                         {
2507                                 $r =& $this->parseResponseHeaders($data, $headers_processed);
2508                                 if ($r)
2509                                 {
2510                                         // failed processing of HTTP response headers
2511                                         // save into response obj the full payload received, for debugging
2512                                         $r->raw_data = $data;
2513                                         return $r;
2514                                 }
2515                         }
2516                         else
2517                         {
2518                                 $GLOBALS['_xh']['headers'] = array();
2519                                 $GLOBALS['_xh']['cookies'] = array();
2520                         }
2521
2522                         if($this->debug)
2523                         {
2524                                 $start = strpos($data, '<!-- SERVER DEBUG INFO (BASE64 ENCODED):');
2525                                 if ($start)
2526                                 {
2527                                         $start += strlen('<!-- SERVER DEBUG INFO (BASE64 ENCODED):');
2528                                         $end = strpos($data, '-->', $start);
2529                                         $comments = substr($data, $start, $end-$start);
2530                                         print "<PRE>---SERVER DEBUG INFO (DECODED) ---\n\t".htmlentities(str_replace("\n", "\n\t", base64_decode($comments)))."\n---END---\n</PRE>";
2531                                 }
2532                         }
2533
2534                         // be tolerant of extra whitespace in response body
2535                         $data = trim($data);
2536
2537                         /// @todo return an error msg if $data=='' ?
2538
2539                         // be tolerant of junk after methodResponse (e.g. javascript ads automatically inserted by free hosts)
2540                         // idea from Luca Mariano <luca.mariano@email.it> originally in PEARified version of the lib
2541                         $pos = strrpos($data, '</methodResponse>');
2542                         if($pos !== false)
2543                         {
2544                                 $data = substr($data, 0, $pos+17);
2545                         }
2546
2547                         // if user wants back raw xml, give it to him
2548                         if ($return_type == 'xml')
2549                         {
2550                                 $r = new xmlrpcresp($data, 0, '', 'xml');
2551                                 $r->hdrs = $GLOBALS['_xh']['headers'];
2552                                 $r->_cookies = $GLOBALS['_xh']['cookies'];
2553                                 $r->raw_data = $raw_data;
2554                                 return $r;
2555                         }
2556
2557                         // try to 'guestimate' the character encoding of the received response
2558                         $resp_encoding = guess_encoding(@$GLOBALS['_xh']['headers']['content-type'], $data);
2559
2560                         $GLOBALS['_xh']['ac']='';
2561                         //$GLOBALS['_xh']['qt']=''; //unused...
2562                         $GLOBALS['_xh']['stack'] = array();
2563                         $GLOBALS['_xh']['valuestack'] = array();
2564                         $GLOBALS['_xh']['isf']=0; // 0 = OK, 1 for xmlrpc fault responses, 2 = invalid xmlrpc
2565                         $GLOBALS['_xh']['isf_reason']='';
2566                         $GLOBALS['_xh']['rt']=''; // 'methodcall or 'methodresponse'
2567
2568                         // if response charset encoding is not known / supported, try to use
2569                         // the default encoding and parse the xml anyway, but log a warning...
2570                         if (!in_array($resp_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
2571                         // the following code might be better for mb_string enabled installs, but
2572                         // makes the lib about 200% slower...
2573                         //if (!is_valid_charset($resp_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
2574                         {
2575                                 error_log('XML-RPC: '.__METHOD__.': invalid charset encoding of received response: '.$resp_encoding);
2576                                 $resp_encoding = $GLOBALS['xmlrpc_defencoding'];
2577                         }
2578                         $parser = xml_parser_create($resp_encoding);
2579                         xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
2580                         // G. Giunta 2005/02/13: PHP internally uses ISO-8859-1, so we have to tell
2581                         // the xml parser to give us back data in the expected charset.
2582                         // What if internal encoding is not in one of the 3 allowed?
2583                         // we use the broadest one, ie. utf8
2584                         // This allows to send data which is native in various charset,
2585                         // by extending xmlrpc_encode_entitites() and setting xmlrpc_internalencoding
2586                         if (!in_array($GLOBALS['xmlrpc_internalencoding'], array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
2587                         {
2588                                 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
2589                         }
2590                         else
2591                         {
2592                                 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $GLOBALS['xmlrpc_internalencoding']);
2593                         }
2594
2595                         if ($return_type == 'phpvals')
2596                         {
2597                                 xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast');
2598                         }
2599                         else
2600                         {
2601                                 xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee');
2602                         }
2603
2604                         xml_set_character_data_handler($parser, 'xmlrpc_cd');
2605                         xml_set_default_handler($parser, 'xmlrpc_dh');
2606
2607                         // first error check: xml not well formed
2608                         if(!xml_parse($parser, $data, count($data)))
2609                         {
2610                                 // thanks to Peter Kocks <peter.kocks@baygate.com>
2611                                 if((xml_get_current_line_number($parser)) == 1)
2612                                 {
2613                                         $errstr = 'XML error at line 1, check URL';
2614                                 }
2615                                 else
2616                                 {
2617                                         $errstr = sprintf('XML error: %s at line %d, column %d',
2618                                                 xml_error_string(xml_get_error_code($parser)),
2619                                                 xml_get_current_line_number($parser), xml_get_current_column_number($parser));
2620                                 }
2621                                 error_log($errstr);
2622                                 $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'], $GLOBALS['xmlrpcstr']['invalid_return'].' ('.$errstr.')');
2623                                 xml_parser_free($parser);
2624                                 if($this->debug)
2625                                 {
2626                                         print $errstr;
2627                                 }
2628                                 $r->hdrs = $GLOBALS['_xh']['headers'];
2629                                 $r->_cookies = $GLOBALS['_xh']['cookies'];
2630                                 $r->raw_data = $raw_data;
2631                                 return $r;
2632                         }
2633                         xml_parser_free($parser);
2634                         // second error check: xml well formed but not xml-rpc compliant
2635                         if ($GLOBALS['_xh']['isf'] > 1)
2636                         {
2637                                 if ($this->debug)
2638                                 {
2639                                         /// @todo echo something for user?
2640                                 }
2641
2642                                 $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'],
2643                                 $GLOBALS['xmlrpcstr']['invalid_return'] . ' ' . $GLOBALS['_xh']['isf_reason']);
2644                         }
2645                         // third error check: parsing of the response has somehow gone boink.
2646                         // NB: shall we omit this check, since we trust the parsing code?
2647                         elseif ($return_type == 'xmlrpcvals' && !is_object($GLOBALS['_xh']['value']))
2648                         {
2649                                 // something odd has happened
2650                                 // and it's time to generate a client side error
2651                                 // indicating something odd went on
2652                                 $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'],
2653                                         $GLOBALS['xmlrpcstr']['invalid_return']);
2654                         }
2655                         else
2656                         {
2657                                 if ($this->debug)
2658                                 {
2659                                         print "<PRE>---PARSED---\n";
2660                                         // somehow htmlentities chokes on var_export, and some full html string...
2661                                         //print htmlentitites(var_export($GLOBALS['_xh']['value'], true));
2662                                         print htmlspecialchars(var_export($GLOBALS['_xh']['value'], true));
2663                                         print "\n---END---</PRE>";
2664                                 }
2665
2666                                 // note that using =& will raise an error if $GLOBALS['_xh']['st'] does not generate an object.
2667                                 $v =& $GLOBALS['_xh']['value'];
2668
2669                                 if($GLOBALS['_xh']['isf'])
2670                                 {
2671                                         /// @todo we should test here if server sent an int and a string,
2672                                         /// and/or coerce them into such...
2673                                         if ($return_type == 'xmlrpcvals')
2674                                         {
2675                                                 $errno_v = $v->structmem('faultCode');
2676                                                 $errstr_v = $v->structmem('faultString');
2677                                                 $errno = $errno_v->scalarval();
2678                                                 $errstr = $errstr_v->scalarval();
2679                                         }
2680                                         else
2681                                         {
2682                                                 $errno = $v['faultCode'];
2683                                                 $errstr = $v['faultString'];
2684                                         }
2685
2686                                         if($errno == 0)
2687                                         {
2688                                                 // FAULT returned, errno needs to reflect that
2689                                                 $errno = -1;
2690                                         }
2691
2692                                         $r = new xmlrpcresp(0, $errno, $errstr);
2693                                 }
2694                                 else
2695                                 {
2696                                         $r=new xmlrpcresp($v, 0, '', $return_type);
2697                                 }
2698                         }
2699
2700                         $r->hdrs = $GLOBALS['_xh']['headers'];
2701                         $r->_cookies = $GLOBALS['_xh']['cookies'];
2702                         $r->raw_data = $raw_data;
2703                         return $r;
2704                 }
2705         }
2706
2707         class xmlrpcval
2708         {
2709                 var $me=array();
2710                 var $mytype=0;
2711                 var $_php_class=null;
2712
2713                 /**
2714                 * @param mixed $val
2715                 * @param string $type any valid xmlrpc type name (lowercase). If null, 'string' is assumed
2716                 */
2717                 function xmlrpcval($val=-1, $type='')
2718                 {
2719                         /// @todo: optimization creep - do not call addXX, do it all inline.
2720                         /// downside: booleans will not be coerced anymore
2721                         if($val!==-1 || $type!='')
2722                         {
2723                                 // optimization creep: inlined all work done by constructor
2724                                 switch($type)
2725                                 {
2726                                         case '':
2727                                                 $this->mytype=1;
2728                                                 $this->me['string']=$val;
2729                                                 break;
2730                                         case 'i4':
2731                                         case 'int':
2732                                         case 'double':
2733                                         case 'string':
2734                                         case 'boolean':
2735                                         case 'dateTime.iso8601':
2736                                         case 'base64':
2737                                         case 'null':
2738                                                 $this->mytype=1;
2739                                                 $this->me[$type]=$val;
2740                                                 break;
2741                                         case 'array':
2742                                                 $this->mytype=2;
2743                                                 $this->me['array']=$val;
2744                                                 break;
2745                                         case 'struct':
2746                                                 $this->mytype=3;
2747                                                 $this->me['struct']=$val;
2748                                                 break;
2749                                         default:
2750                                                 error_log("XML-RPC: ".__METHOD__.": not a known type ($type)");
2751                                 }
2752                                 /*if($type=='')
2753                                 {
2754                                         $type='string';
2755                                 }
2756                                 if($GLOBALS['xmlrpcTypes'][$type]==1)
2757                                 {
2758                                         $this->addScalar($val,$type);
2759                                 }
2760                                 elseif($GLOBALS['xmlrpcTypes'][$type]==2)
2761                                 {
2762                                         $this->addArray($val);
2763                                 }
2764                                 elseif($GLOBALS['xmlrpcTypes'][$type]==3)
2765                                 {
2766                                         $this->addStruct($val);
2767                                 }*/
2768                         }
2769                 }
2770
2771                 /**
2772                 * Add a single php value to an (unitialized) xmlrpcval
2773                 * @param mixed $val
2774                 * @param string $type
2775                 * @return int 1 or 0 on failure
2776                 */
2777                 function addScalar($val, $type='string')
2778                 {
2779                         $typeof=@$GLOBALS['xmlrpcTypes'][$type];
2780                         if($typeof!=1)
2781                         {
2782                                 error_log("XML-RPC: ".__METHOD__.": not a scalar type ($type)");
2783                                 return 0;
2784                         }
2785
2786                         // coerce booleans into correct values
2787                         // NB: we should either do it for datetimes, integers and doubles, too,
2788                         // or just plain remove this check, implemented on booleans only...
2789                         if($type==$GLOBALS['xmlrpcBoolean'])
2790                         {
2791                                 if(strcasecmp($val,'true')==0 || $val==1 || ($val==true && strcasecmp($val,'false')))
2792                                 {
2793                                         $val=true;
2794                                 }
2795                                 else
2796                                 {
2797                                         $val=false;
2798                                 }
2799                         }
2800
2801                         switch($this->mytype)
2802                         {
2803                                 case 1:
2804                                         error_log('XML-RPC: '.__METHOD__.': scalar xmlrpcval can have only one value');
2805                                         return 0;
2806                                 case 3:
2807                                         error_log('XML-RPC: '.__METHOD__.': cannot add anonymous scalar to struct xmlrpcval');
2808                                         return 0;
2809                                 case 2:
2810                                         // we're adding a scalar value to an array here
2811                                         //$ar=$this->me['array'];
2812                                         //$ar[]=new xmlrpcval($val, $type);
2813                                         //$this->me['array']=$ar;
2814                                         // Faster (?) avoid all the costly array-copy-by-val done here...
2815                                         $this->me['array'][]=new xmlrpcval($val, $type);
2816                                         return 1;
2817                                 default:
2818                                         // a scalar, so set the value and remember we're scalar
2819                                         $this->me[$type]=$val;
2820                                         $this->mytype=$typeof;
2821                                         return 1;
2822                         }
2823                 }
2824
2825                 /**
2826                 * Add an array of xmlrpcval objects to an xmlrpcval
2827                 * @param array $vals
2828                 * @return int 1 or 0 on failure
2829                 * @access public
2830                 *
2831                 * @todo add some checking for $vals to be an array of xmlrpcvals?
2832                 */
2833                 function addArray($vals)
2834                 {
2835                         if($this->mytype==0)
2836                         {
2837                                 $this->mytype=$GLOBALS['xmlrpcTypes']['array'];
2838                                 $this->me['array']=$vals;
2839                                 return 1;
2840                         }
2841                         elseif($this->mytype==2)
2842                         {
2843                                 // we're adding to an array here
2844                                 $this->me['array'] = array_merge($this->me['array'], $vals);
2845                                 return 1;
2846                         }
2847                         else
2848                         {
2849                                 error_log('XML-RPC: '.__METHOD__.': already initialized as a [' . $this->kindOf() . ']');
2850                                 return 0;
2851                         }
2852                 }
2853
2854                 /**
2855                 * Add an array of named xmlrpcval objects to an xmlrpcval
2856                 * @param array $vals
2857                 * @return int 1 or 0 on failure
2858                 * @access public
2859                 *
2860                 * @todo add some checking for $vals to be an array?
2861                 */
2862                 function addStruct($vals)
2863                 {
2864                         if($this->mytype==0)
2865                         {
2866                                 $this->mytype=$GLOBALS['xmlrpcTypes']['struct'];
2867                                 $this->me['struct']=$vals;
2868                                 return 1;
2869                         }
2870                         elseif($this->mytype==3)
2871                         {
2872                                 // we're adding to a struct here
2873                                 $this->me['struct'] = array_merge($this->me['struct'], $vals);
2874                                 return 1;
2875                         }
2876                         else
2877                         {
2878                                 error_log('XML-RPC: '.__METHOD__.': already initialized as a [' . $this->kindOf() . ']');
2879                                 return 0;
2880                         }
2881                 }
2882
2883                 // poor man's version of print_r ???
2884                 // DEPRECATED!
2885                 function dump($ar)
2886                 {
2887                         foreach($ar as $key => $val)
2888                         {
2889                                 echo "$key => $val<br />";
2890                                 if($key == 'array')
2891                                 {
2892                                         while(list($key2, $val2) = each($val))
2893                                         {
2894                                                 echo "-- $key2 => $val2<br />";
2895                                         }
2896                                 }
2897                         }
2898                 }
2899
2900                 /**
2901                 * Returns a string containing "struct", "array" or "scalar" describing the base type of the value
2902                 * @return string
2903                 * @access public
2904                 */
2905                 function kindOf()
2906                 {
2907                         switch($this->mytype)
2908                         {
2909                                 case 3:
2910                                         return 'struct';
2911                                         break;
2912                                 case 2:
2913                                         return 'array';
2914                                         break;
2915                                 case 1:
2916                                         return 'scalar';
2917                                         break;
2918                                 default:
2919                                         return 'undef';
2920                         }
2921                 }
2922
2923                 /**
2924                 * @access private
2925                 */
2926                 function serializedata($typ, $val, $charset_encoding='')
2927                 {
2928                         $rs='';
2929                         switch(@$GLOBALS['xmlrpcTypes'][$typ])
2930                         {
2931                                 case 1:
2932                                         switch($typ)
2933                                         {
2934                                                 case $GLOBALS['xmlrpcBase64']:
2935                                                         $rs.="<${typ}>" . base64_encode($val) . "</${typ}>";
2936                                                         break;
2937                                                 case $GLOBALS['xmlrpcBoolean']:
2938                                                         $rs.="<${typ}>" . ($val ? '1' : '0') . "</${typ}>";
2939                                                         break;
2940                                                 case $GLOBALS['xmlrpcString']:
2941                                                         // G. Giunta 2005/2/13: do NOT use htmlentities, since
2942                                                         // it will produce named html entities, which are invalid xml
2943                                                         $rs.="<${typ}>" . xmlrpc_encode_entitites($val, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding). "</${typ}>";
2944                                                         break;
2945                                                 case $GLOBALS['xmlrpcInt']:
2946                                                 case $GLOBALS['xmlrpcI4']:
2947                                                         $rs.="<${typ}>".(int)$val."</${typ}>";
2948                                                         break;
2949                                                 case $GLOBALS['xmlrpcDouble']:
2950                                                         // avoid using standard conversion of float to string because it is locale-dependent,
2951                                                         // and also because the xmlrpc spec forbids exponential notation.
2952                                                         // sprintf('%F') could be most likely ok but it fails eg. on 2e-14.
2953                                                         // The code below tries its best at keeping max precision while avoiding exp notation,
2954                                                         // but there is of course no limit in the number of decimal places to be used...
2955                                                         $rs.="<${typ}>".preg_replace('/\\.?0+$/','',number_format((double)$val, 128, '.', ''))."</${typ}>";
2956                                                         break;
2957                                                 case $GLOBALS['xmlrpcDateTime']:
2958                                                         if (is_string($val))
2959                                                         {
2960                                                                 $rs.="<${typ}>${val}</${typ}>";
2961                                                         }
2962                                                         else if(is_a($val, 'DateTime'))
2963                                                         {
2964                                                                 $rs.="<${typ}>".$val->format('Ymd\TH:i:s')."</${typ}>";
2965                                                         }
2966                                                         else if(is_int($val))
2967                                                         {
2968                                                                 $rs.="<${typ}>".strftime("%Y%m%dT%H:%M:%S", $val)."</${typ}>";
2969                                                         }
2970                                                         else
2971                                                         {
2972                                                                 // not really a good idea here: but what shall we output anyway? left for backward compat...
2973                                                                 $rs.="<${typ}>${val}</${typ}>";
2974                                                         }
2975                                                         break;
2976                                                 case $GLOBALS['xmlrpcNull']:
2977                                                         if ($GLOBALS['xmlrpc_null_apache_encoding'])
2978                                                         {
2979                                                                 $rs.="<ex:nil/>";
2980                                                         }
2981                                                         else
2982                                                         {
2983                                                                 $rs.="<nil/>";
2984                                                         }
2985                                                         break;
2986                                                 default:
2987                                                         // no standard type value should arrive here, but provide a possibility
2988                                                         // for xmlrpcvals of unknown type...
2989                                                         $rs.="<${typ}>${val}</${typ}>";
2990                                         }
2991                                         break;
2992                                 case 3:
2993                                         // struct
2994                                         if ($this->_php_class)
2995                                         {
2996                                                 $rs.='<struct php_class="' . $this->_php_class . "\">\n";
2997                                         }
2998                                         else
2999                                         {
3000                                                 $rs.="<struct>\n";
3001                                         }
3002                                         foreach($val as $key2 => $val2)
3003                                         {
3004                                                 $rs.='<member><name>'.xmlrpc_encode_entitites($key2, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding)."</name>\n";
3005                                                 //$rs.=$this->serializeval($val2);
3006                                                 $rs.=$val2->serialize($charset_encoding);
3007                                                 $rs.="</member>\n";
3008                                         }
3009                                         $rs.='</struct>';
3010                                         break;
3011                                 case 2:
3012                                         // array
3013                                         $rs.="<array>\n<data>\n";
3014                                         for($i=0; $i<count($val); $i++)
3015                                         {
3016                                                 //$rs.=$this->serializeval($val[$i]);
3017                                                 $rs.=$val[$i]->serialize($charset_encoding);
3018                                         }
3019                                         $rs.="</data>\n</array>";
3020                                         break;
3021                                 default:
3022                                         break;
3023                         }
3024                         return $rs;
3025                 }
3026
3027                 /**
3028                 * Returns xml representation of the value. XML prologue not included
3029                 * @param string $charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed
3030                 * @return string
3031                 * @access public
3032                 */
3033                 function serialize($charset_encoding='')
3034                 {
3035                         // add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals...
3036                         //if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval')))
3037                         //{
3038                                 reset($this->me);
3039                                 list($typ, $val) = each($this->me);
3040                                 return '<value>' . $this->serializedata($typ, $val, $charset_encoding) . "</value>\n";
3041                         //}
3042                 }
3043
3044                 // DEPRECATED
3045                 function serializeval($o)
3046                 {
3047                         // add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals...
3048                         //if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval')))
3049                         //{
3050                                 $ar=$o->me;
3051                                 reset($ar);
3052                                 list($typ, $val) = each($ar);
3053                                 return '<value>' . $this->serializedata($typ, $val) . "</value>\n";
3054                         //}
3055                 }
3056
3057                 /**
3058                 * Checks wheter a struct member with a given name is present.
3059                 * Works only on xmlrpcvals of type struct.
3060                 * @param string $m the name of the struct member to be looked up
3061                 * @return boolean
3062                 * @access public
3063                 */
3064                 function structmemexists($m)
3065                 {
3066                         return array_key_exists($m, $this->me['struct']);
3067                 }
3068
3069                 /**
3070                 * Returns the value of a given struct member (an xmlrpcval object in itself).
3071                 * Will raise a php warning if struct member of given name does not exist
3072                 * @param string $m the name of the struct member to be looked up
3073                 * @return xmlrpcval
3074                 * @access public
3075                 */
3076                 function structmem($m)
3077                 {
3078                         return $this->me['struct'][$m];
3079                 }
3080
3081                 /**
3082                 * Reset internal pointer for xmlrpcvals of type struct.
3083                 * @access public
3084                 */
3085                 function structreset()
3086                 {
3087                         reset($this->me['struct']);
3088                 }
3089
3090                 /**
3091                 * Return next member element for xmlrpcvals of type struct.
3092                 * @return xmlrpcval
3093                 * @access public
3094                 */
3095                 function structeach()
3096                 {
3097                         return each($this->me['struct']);
3098                 }
3099
3100                 // DEPRECATED! this code looks like it is very fragile and has not been fixed
3101                 // for a long long time. Shall we remove it for 2.0?
3102                 function getval()
3103                 {
3104                         // UNSTABLE
3105                         reset($this->me);
3106                         list($a,$b)=each($this->me);
3107                         // contributed by I Sofer, 2001-03-24
3108                         // add support for nested arrays to scalarval
3109                         // i've created a new method here, so as to
3110                         // preserve back compatibility
3111
3112                         if(is_array($b))
3113                         {
3114                                 @reset($b);
3115                                 while(list($id,$cont) = @each($b))
3116                                 {
3117                                         $b[$id] = $cont->scalarval();
3118                                 }
3119                         }
3120
3121                         // add support for structures directly encoding php objects
3122                         if(is_object($b))
3123                         {
3124                                 $t = get_object_vars($b);
3125                                 @reset($t);
3126                                 while(list($id,$cont) = @each($t))
3127                                 {
3128                                         $t[$id] = $cont->scalarval();
3129                                 }
3130                                 @reset($t);
3131                                 while(list($id,$cont) = @each($t))
3132                                 {
3133                                         @$b->$id = $cont;
3134                                 }
3135                         }
3136                         // end contrib
3137                         return $b;
3138                 }
3139
3140                 /**
3141                 * Returns the value of a scalar xmlrpcval
3142                 * @return mixed
3143                 * @access public
3144                 */
3145                 function scalarval()
3146                 {
3147                         reset($this->me);
3148                         list(,$b)=each($this->me);
3149                         return $b;
3150                 }
3151
3152                 /**
3153                 * Returns the type of the xmlrpcval.
3154                 * For integers, 'int' is always returned in place of 'i4'
3155                 * @return string
3156                 * @access public
3157                 */
3158                 function scalartyp()
3159                 {
3160                         reset($this->me);
3161                         list($a,)=each($this->me);
3162                         if($a==$GLOBALS['xmlrpcI4'])
3163                         {
3164                                 $a=$GLOBALS['xmlrpcInt'];
3165                         }
3166                         return $a;
3167                 }
3168
3169                 /**
3170                 * Returns the m-th member of an xmlrpcval of struct type
3171                 * @param integer $m the index of the value to be retrieved (zero based)
3172                 * @return xmlrpcval
3173                 * @access public
3174                 */
3175                 function arraymem($m)
3176                 {
3177                         return $this->me['array'][$m];
3178                 }
3179
3180                 /**
3181                 * Returns the number of members in an xmlrpcval of array type
3182                 * @return integer
3183                 * @access public
3184                 */
3185                 function arraysize()
3186                 {
3187                         return count($this->me['array']);
3188                 }
3189
3190                 /**
3191                 * Returns the number of members in an xmlrpcval of struct type
3192                 * @return integer
3193                 * @access public
3194                 */
3195                 function structsize()
3196                 {
3197                         return count($this->me['struct']);
3198                 }
3199         }
3200
3201
3202         // date helpers
3203
3204         /**
3205         * Given a timestamp, return the corresponding ISO8601 encoded string.
3206         *
3207         * Really, timezones ought to be supported
3208         * but the XML-RPC spec says:
3209         *
3210         * "Don't assume a timezone. It should be specified by the server in its
3211         * documentation what assumptions it makes about timezones."
3212         *
3213         * These routines always assume localtime unless
3214         * $utc is set to 1, in which case UTC is assumed
3215         * and an adjustment for locale is made when encoding
3216         *
3217         * @param int $timet (timestamp)
3218         * @param int $utc (0 or 1)
3219         * @return string
3220         */
3221         function iso8601_encode($timet, $utc=0)
3222         {
3223                 if(!$utc)
3224                 {
3225                         $t=strftime("%Y%m%dT%H:%M:%S", $timet);
3226                 }
3227                 else
3228                 {
3229                         if(function_exists('gmstrftime'))
3230                         {
3231                                 // gmstrftime doesn't exist in some versions
3232                                 // of PHP
3233                                 $t=gmstrftime("%Y%m%dT%H:%M:%S", $timet);
3234                         }
3235                         else
3236                         {
3237                                 $t=strftime("%Y%m%dT%H:%M:%S", $timet-date('Z'));
3238                         }
3239                 }
3240                 return $t;
3241         }
3242
3243         /**
3244         * Given an ISO8601 date string, return a timet in the localtime, or UTC
3245         * @param string $idate
3246         * @param int $utc either 0 or 1
3247         * @return int (datetime)
3248         */
3249         function iso8601_decode($idate, $utc=0)
3250         {
3251                 $t=0;
3252                 if(preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})/', $idate, $regs))
3253                 {
3254                         if($utc)
3255                         {
3256                                 $t=gmmktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
3257                         }
3258                         else
3259                         {
3260                                 $t=mktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
3261                         }
3262                 }
3263                 return $t;
3264         }
3265
3266         /**
3267         * Takes an xmlrpc value in PHP xmlrpcval object format and translates it into native PHP types.
3268         *
3269         * Works with xmlrpc message objects as input, too.
3270         *
3271         * Given proper options parameter, can rebuild generic php object instances
3272         * (provided those have been encoded to xmlrpc format using a corresponding
3273         * option in php_xmlrpc_encode())
3274         * PLEASE NOTE that rebuilding php objects involves calling their constructor function.
3275         * This means that the remote communication end can decide which php code will
3276         * get executed on your server, leaving the door possibly open to 'php-injection'
3277         * style of attacks (provided you have some classes defined on your server that
3278         * might wreak havoc if instances are built outside an appropriate context).
3279         * Make sure you trust the remote server/client before eanbling this!
3280         *
3281         * @author Dan Libby (dan@libby.com)
3282         *
3283         * @param xmlrpcval $xmlrpc_val
3284         * @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
3285         * @return mixed
3286         */
3287         function php_xmlrpc_decode($xmlrpc_val, $options=array())
3288         {
3289                 switch($xmlrpc_val->kindOf())
3290                 {
3291                         case 'scalar':
3292                                 if (in_array('extension_api', $options))
3293                                 {
3294                                         reset($xmlrpc_val->me);
3295                                         list($typ,$val) = each($xmlrpc_val->me);
3296                                         switch ($typ)
3297                                         {
3298                                                 case 'dateTime.iso8601':
3299                                                         $xmlrpc_val->scalar = $val;
3300                                                         $xmlrpc_val->xmlrpc_type = 'datetime';
3301                                                         $xmlrpc_val->timestamp = iso8601_decode($val);
3302                                                         return $xmlrpc_val;
3303                                                 case 'base64':
3304                                                         $xmlrpc_val->scalar = $val;
3305                                                         $xmlrpc_val->type = $typ;
3306                                                         return $xmlrpc_val;
3307                                                 default:
3308                                                         return $xmlrpc_val->scalarval();
3309                                         }
3310                                 }
3311                                 if (in_array('dates_as_objects', $options) && $xmlrpc_val->scalartyp() == 'dateTime.iso8601')
3312                                 {
3313                                         // we return a Datetime object instead of a string
3314                                         // since now the constructor of xmlrpcval accepts safely strings, ints and datetimes,
3315                                         // we cater to all 3 cases here
3316                                         $out = $xmlrpc_val->scalarval();
3317                                         if (is_string($out))
3318                                         {
3319                                                 $out = strtotime($out);
3320                                         }
3321                                         if (is_int($out))
3322                                         {
3323                                                 $result = new Datetime();
3324                                                 $result->setTimestamp($out);
3325                                                 return $result;
3326                                         }
3327                                         elseif (is_a($out, 'Datetime'))
3328                                         {
3329                                                 return $out;
3330                                         }
3331                                 }
3332                                 return $xmlrpc_val->scalarval();
3333                         case 'array':
3334                                 $size = $xmlrpc_val->arraysize();
3335                                 $arr = array();
3336                                 for($i = 0; $i < $size; $i++)
3337                                 {
3338                                         $arr[] = php_xmlrpc_decode($xmlrpc_val->arraymem($i), $options);
3339                                 }
3340                                 return $arr;
3341                         case 'struct':
3342                                 $xmlrpc_val->structreset();
3343                                 // If user said so, try to rebuild php objects for specific struct vals.
3344                                 /// @todo should we raise a warning for class not found?
3345                                 // shall we check for proper subclass of xmlrpcval instead of
3346                                 // presence of _php_class to detect what we can do?
3347                                 if (in_array('decode_php_objs', $options) && $xmlrpc_val->_php_class != ''
3348                                         && class_exists($xmlrpc_val->_php_class))
3349                                 {
3350                                         $obj = @new $xmlrpc_val->_php_class;
3351                                         while(list($key,$value)=$xmlrpc_val->structeach())
3352                                         {
3353                                                 $obj->$key = php_xmlrpc_decode($value, $options);
3354                                         }
3355                                         return $obj;
3356                                 }
3357                                 else
3358                                 {
3359                                         $arr = array();
3360                                         while(list($key,$value)=$xmlrpc_val->structeach())
3361                                         {
3362                                                 $arr[$key] = php_xmlrpc_decode($value, $options);
3363                                         }
3364                                         return $arr;
3365                                 }
3366                         case 'msg':
3367                                 $paramcount = $xmlrpc_val->getNumParams();
3368                                 $arr = array();
3369                                 for($i = 0; $i < $paramcount; $i++)
3370                                 {
3371                                         $arr[] = php_xmlrpc_decode($xmlrpc_val->getParam($i));
3372                                 }
3373                                 return $arr;
3374                         }
3375         }
3376
3377         // This constant left here only for historical reasons...
3378         // it was used to decide if we have to define xmlrpc_encode on our own, but
3379         // we do not do it anymore
3380         if(function_exists('xmlrpc_decode'))
3381         {
3382                 define('XMLRPC_EPI_ENABLED','1');
3383         }
3384         else
3385         {
3386                 define('XMLRPC_EPI_ENABLED','0');
3387         }
3388
3389         /**
3390         * Takes native php types and encodes them into xmlrpc PHP object format.
3391         * It will not re-encode xmlrpcval objects.
3392         *
3393         * Feature creep -- could support more types via optional type argument
3394         * (string => datetime support has been added, ??? => base64 not yet)
3395         *
3396         * If given a proper options parameter, php object instances will be encoded
3397         * into 'special' xmlrpc values, that can later be decoded into php objects
3398         * by calling php_xmlrpc_decode() with a corresponding option
3399         *
3400         * @author Dan Libby (dan@libby.com)
3401         *
3402         * @param mixed $php_val the value to be converted into an xmlrpcval object
3403         * @param array $options can include 'encode_php_objs', 'auto_dates', 'null_extension' or 'extension_api'
3404         * @return xmlrpcval
3405         */
3406         function php_xmlrpc_encode($php_val, $options=array())
3407         {
3408                 $type = gettype($php_val);
3409                 switch($type)
3410                 {
3411                         case 'string':
3412                                 if (in_array('auto_dates', $options) && preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $php_val))
3413                                         $xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcDateTime']);
3414                                 else
3415                                         $xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcString']);
3416                                 break;
3417                         case 'integer':
3418                                 $xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcInt']);
3419                                 break;
3420                         case 'double':
3421                                 $xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcDouble']);
3422                                 break;
3423                                 // <G_Giunta_2001-02-29>
3424                                 // Add support for encoding/decoding of booleans, since they are supported in PHP
3425                         case 'boolean':
3426                                 $xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcBoolean']);
3427                                 break;
3428                                 // </G_Giunta_2001-02-29>
3429                         case 'array':
3430                                 // PHP arrays can be encoded to either xmlrpc structs or arrays,
3431                                 // depending on wheter they are hashes or plain 0..n integer indexed
3432                                 // A shorter one-liner would be
3433                                 // $tmp = array_diff(array_keys($php_val), range(0, count($php_val)-1));
3434                                 // but execution time skyrockets!
3435                                 $j = 0;
3436                                 $arr = array();
3437                                 $ko = false;
3438                                 foreach($php_val as $key => $val)
3439                                 {
3440                                         $arr[$key] = php_xmlrpc_encode($val, $options);
3441                                         if(!$ko && $key !== $j)
3442                                         {
3443                                                 $ko = true;
3444                                         }
3445                                         $j++;
3446                                 }
3447                                 if($ko)
3448                                 {
3449                                         $xmlrpc_val = new xmlrpcval($arr, $GLOBALS['xmlrpcStruct']);
3450                                 }
3451                                 else
3452                                 {
3453                                         $xmlrpc_val = new xmlrpcval($arr, $GLOBALS['xmlrpcArray']);
3454                                 }
3455                                 break;
3456                         case 'object':
3457                                 if(is_a($php_val, 'xmlrpcval'))
3458                                 {
3459                                         $xmlrpc_val = $php_val;
3460                                 }
3461                                 else if(is_a($php_val, 'DateTime'))
3462                                 {
3463                                         $xmlrpc_val = new xmlrpcval($php_val->format('Ymd\TH:i:s'), $GLOBALS['xmlrpcStruct']);
3464                                 }
3465                                 else
3466                                 {
3467                                         $arr = array();
3468                                         reset($php_val);
3469                                         while(list($k,$v) = each($php_val))
3470                                         {
3471                                                 $arr[$k] = php_xmlrpc_encode($v, $options);
3472                                         }
3473                                         $xmlrpc_val = new xmlrpcval($arr, $GLOBALS['xmlrpcStruct']);
3474                                         if (in_array('encode_php_objs', $options))
3475                                         {
3476                                                 // let's save original class name into xmlrpcval:
3477                                                 // might be useful later on...
3478                                                 $xmlrpc_val->_php_class = get_class($php_val);
3479                                         }
3480                                 }
3481                                 break;
3482                         case 'NULL':
3483                                 if (in_array('extension_api', $options))
3484                                 {
3485                                         $xmlrpc_val = new xmlrpcval('', $GLOBALS['xmlrpcString']);
3486                                 }
3487                                 else if (in_array('null_extension', $options))
3488                                 {
3489                                         $xmlrpc_val = new xmlrpcval('', $GLOBALS['xmlrpcNull']);
3490                                 }
3491                                 else
3492                                 {
3493                                         $xmlrpc_val = new xmlrpcval();
3494                                 }
3495                                 break;
3496                         case 'resource':
3497                                 if (in_array('extension_api', $options))
3498                                 {
3499                                         $xmlrpc_val = new xmlrpcval((int)$php_val, $GLOBALS['xmlrpcInt']);
3500                                 }
3501                                 else
3502                                 {
3503                                         $xmlrpc_val = new xmlrpcval();
3504                                 }
3505                         // catch "user function", "unknown type"
3506                         default:
3507                                 // giancarlo pinerolo <ping@alt.it>
3508                                 // it has to return
3509                                 // an empty object in case, not a boolean.
3510                                 $xmlrpc_val = new xmlrpcval();
3511                                 break;
3512                         }
3513                         return $xmlrpc_val;
3514         }
3515
3516         /**
3517         * Convert the xml representation of a method response, method request or single
3518         * xmlrpc value into the appropriate object (a.k.a. deserialize)
3519         * @param string $xml_val
3520         * @param array $options
3521         * @return mixed false on error, or an instance of either xmlrpcval, xmlrpcmsg or xmlrpcresp
3522         */
3523         function php_xmlrpc_decode_xml($xml_val, $options=array())
3524         {
3525                 $GLOBALS['_xh'] = array();
3526                 $GLOBALS['_xh']['ac'] = '';
3527                 $GLOBALS['_xh']['stack'] = array();
3528                 $GLOBALS['_xh']['valuestack'] = array();
3529                 $GLOBALS['_xh']['params'] = array();
3530                 $GLOBALS['_xh']['pt'] = array();
3531                 $GLOBALS['_xh']['isf'] = 0;
3532                 $GLOBALS['_xh']['isf_reason'] = '';
3533                 $GLOBALS['_xh']['method'] = false;
3534                 $GLOBALS['_xh']['rt'] = '';
3535                 /// @todo 'guestimate' encoding
3536                 $parser = xml_parser_create();
3537                 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
3538                 // What if internal encoding is not in one of the 3 allowed?
3539                 // we use the broadest one, ie. utf8!
3540                 if (!in_array($GLOBALS['xmlrpc_internalencoding'], array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
3541                 {
3542                         xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
3543                 }
3544                 else
3545                 {
3546                         xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $GLOBALS['xmlrpc_internalencoding']);
3547                 }
3548                 xml_set_element_handler($parser, 'xmlrpc_se_any', 'xmlrpc_ee');
3549                 xml_set_character_data_handler($parser, 'xmlrpc_cd');
3550                 xml_set_default_handler($parser, 'xmlrpc_dh');
3551                 if(!xml_parse($parser, $xml_val, 1))
3552                 {
3553                         $errstr = sprintf('XML error: %s at line %d, column %d',
3554                                                 xml_error_string(xml_get_error_code($parser)),
3555                                                 xml_get_current_line_number($parser), xml_get_current_column_number($parser));
3556                         error_log($errstr);
3557                         xml_parser_free($parser);
3558                         return false;
3559                 }
3560                 xml_parser_free($parser);
3561                 if ($GLOBALS['_xh']['isf'] > 1) // test that $GLOBALS['_xh']['value'] is an obj, too???
3562                 {
3563                         error_log($GLOBALS['_xh']['isf_reason']);
3564                         return false;
3565                 }
3566                 switch ($GLOBALS['_xh']['rt'])
3567                 {
3568                         case 'methodresponse':
3569                                 $v =& $GLOBALS['_xh']['value'];
3570                                 if ($GLOBALS['_xh']['isf'] == 1)
3571                                 {
3572                                         $vc = $v->structmem('faultCode');
3573                                         $vs = $v->structmem('faultString');
3574                                         $r = new xmlrpcresp(0, $vc->scalarval(), $vs->scalarval());
3575                                 }
3576                                 else
3577                                 {
3578                                         $r = new xmlrpcresp($v);
3579                                 }
3580                                 return $r;
3581                         case 'methodcall':
3582                                 $m = new xmlrpcmsg($GLOBALS['_xh']['method']);
3583                                 for($i=0; $i < count($GLOBALS['_xh']['params']); $i++)
3584                                 {
3585                                         $m->addParam($GLOBALS['_xh']['params'][$i]);
3586                                 }
3587                                 return $m;
3588                         case 'value':
3589                                 return $GLOBALS['_xh']['value'];
3590                         default:
3591                                 return false;
3592                 }
3593         }
3594
3595         /**
3596         * decode a string that is encoded w/ "chunked" transfer encoding
3597         * as defined in rfc2068 par. 19.4.6
3598         * code shamelessly stolen from nusoap library by Dietrich Ayala
3599         *
3600         * @param string $buffer the string to be decoded
3601         * @return string
3602         */
3603         function decode_chunked($buffer)
3604         {
3605                 // length := 0
3606                 $length = 0;
3607                 $new = '';
3608
3609                 // read chunk-size, chunk-extension (if any) and crlf
3610                 // get the position of the linebreak
3611                 $chunkend = strpos($buffer,"\r\n") + 2;
3612                 $temp = substr($buffer,0,$chunkend);
3613                 $chunk_size = hexdec( trim($temp) );
3614                 $chunkstart = $chunkend;
3615                 while($chunk_size > 0)
3616                 {
3617                         $chunkend = strpos($buffer, "\r\n", $chunkstart + $chunk_size);
3618
3619                         // just in case we got a broken connection
3620                         if($chunkend == false)
3621                         {
3622                                 $chunk = substr($buffer,$chunkstart);
3623                                 // append chunk-data to entity-body
3624                                 $new .= $chunk;
3625                                 $length += strlen($chunk);
3626                                 break;
3627                         }
3628
3629                         // read chunk-data and crlf
3630                         $chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart);
3631                         // append chunk-data to entity-body
3632                         $new .= $chunk;
3633                         // length := length + chunk-size
3634                         $length += strlen($chunk);
3635                         // read chunk-size and crlf
3636                         $chunkstart = $chunkend + 2;
3637
3638                         $chunkend = strpos($buffer,"\r\n",$chunkstart)+2;
3639                         if($chunkend == false)
3640                         {
3641                                 break; //just in case we got a broken connection
3642                         }
3643                         $temp = substr($buffer,$chunkstart,$chunkend-$chunkstart);
3644                         $chunk_size = hexdec( trim($temp) );
3645                         $chunkstart = $chunkend;
3646                 }
3647                 return $new;
3648         }
3649
3650         /**
3651         * xml charset encoding guessing helper function.
3652         * Tries to determine the charset encoding of an XML chunk received over HTTP.
3653         * NB: according to the spec (RFC 3023), if text/xml content-type is received over HTTP without a content-type,
3654         * we SHOULD assume it is strictly US-ASCII. But we try to be more tolerant of unconforming (legacy?) clients/servers,
3655         * which will be most probably using UTF-8 anyway...
3656         *
3657         * @param string $httpheaders the http Content-type header
3658         * @param string $xmlchunk xml content buffer
3659         * @param string $encoding_prefs comma separated list of character encodings to be used as default (when mb extension is enabled)
3660         *
3661         * @todo explore usage of mb_http_input(): does it detect http headers + post data? if so, use it instead of hand-detection!!!
3662         */
3663         function guess_encoding($httpheader='', $xmlchunk='', $encoding_prefs=null)
3664         {
3665                 // discussion: see http://www.yale.edu/pclt/encoding/
3666                 // 1 - test if encoding is specified in HTTP HEADERS
3667
3668                 //Details:
3669                 // LWS:           (\13\10)?( |\t)+
3670                 // token:         (any char but excluded stuff)+
3671                 // quoted string: " (any char but double quotes and cointrol chars)* "
3672                 // header:        Content-type = ...; charset=value(; ...)*
3673                 //   where value is of type token, no LWS allowed between 'charset' and value
3674                 // Note: we do not check for invalid chars in VALUE:
3675                 //   this had better be done using pure ereg as below
3676                 // Note 2: we might be removing whitespace/tabs that ought to be left in if
3677                 //   the received charset is a quoted string. But nobody uses such charset names...
3678
3679                 /// @todo this test will pass if ANY header has charset specification, not only Content-Type. Fix it?
3680                 $matches = array();
3681                 if(preg_match('/;\s*charset\s*=([^;]+)/i', $httpheader, $matches))
3682                 {
3683                         return strtoupper(trim($matches[1], " \t\""));
3684                 }
3685
3686                 // 2 - scan the first bytes of the data for a UTF-16 (or other) BOM pattern
3687                 //     (source: http://www.w3.org/TR/2000/REC-xml-20001006)
3688                 //     NOTE: actually, according to the spec, even if we find the BOM and determine
3689                 //     an encoding, we should check if there is an encoding specified
3690                 //     in the xml declaration, and verify if they match.
3691                 /// @todo implement check as described above?
3692                 /// @todo implement check for first bytes of string even without a BOM? (It sure looks harder than for cases WITH a BOM)
3693                 if(preg_match('/^(\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\x00\x00\xFF\xFE|\xFE\xFF\x00\x00)/', $xmlchunk))
3694                 {
3695                         return 'UCS-4';
3696                 }
3697                 elseif(preg_match('/^(\xFE\xFF|\xFF\xFE)/', $xmlchunk))
3698                 {
3699                         return 'UTF-16';
3700                 }
3701                 elseif(preg_match('/^(\xEF\xBB\xBF)/', $xmlchunk))
3702                 {
3703                         return 'UTF-8';
3704                 }
3705
3706                 // 3 - test if encoding is specified in the xml declaration
3707                 // Details:
3708                 // SPACE:         (#x20 | #x9 | #xD | #xA)+ === [ \x9\xD\xA]+
3709                 // EQ:            SPACE?=SPACE? === [ \x9\xD\xA]*=[ \x9\xD\xA]*
3710                 if (preg_match('/^<\?xml\s+version\s*=\s*'. "((?:\"[a-zA-Z0-9_.:-]+\")|(?:'[a-zA-Z0-9_.:-]+'))".
3711                         '\s+encoding\s*=\s*' . "((?:\"[A-Za-z][A-Za-z0-9._-]*\")|(?:'[A-Za-z][A-Za-z0-9._-]*'))/",
3712                         $xmlchunk, $matches))
3713                 {
3714                         return strtoupper(substr($matches[2], 1, -1));
3715                 }
3716
3717                 // 4 - if mbstring is available, let it do the guesswork
3718                 // NB: we favour finding an encoding that is compatible with what we can process
3719                 if(extension_loaded('mbstring'))
3720                 {
3721                         if($encoding_prefs)
3722                         {
3723                                 $enc = mb_detect_encoding($xmlchunk, $encoding_prefs);
3724                         }
3725                         else
3726                         {
3727                                 $enc = mb_detect_encoding($xmlchunk);
3728                         }
3729                         // NB: mb_detect likes to call it ascii, xml parser likes to call it US_ASCII...
3730                         // IANA also likes better US-ASCII, so go with it
3731                         if($enc == 'ASCII')
3732                         {
3733                                 $enc = 'US-'.$enc;
3734                         }
3735                         return $enc;
3736                 }
3737                 else
3738                 {
3739                         // no encoding specified: as per HTTP1.1 assume it is iso-8859-1?
3740                         // Both RFC 2616 (HTTP 1.1) and 1945 (HTTP 1.0) clearly state that for text/xxx content types
3741                         // this should be the standard. And we should be getting text/xml as request and response.
3742                         // BUT we have to be backward compatible with the lib, which always used UTF-8 as default...
3743                         return $GLOBALS['xmlrpc_defencoding'];
3744                 }
3745         }
3746
3747         /**
3748         * Checks if a given charset encoding is present in a list of encodings or
3749         * if it is a valid subset of any encoding in the list
3750         * @param string $encoding charset to be tested
3751         * @param mixed $validlist comma separated list of valid charsets (or array of charsets)
3752         */
3753         function is_valid_charset($encoding, $validlist)
3754         {
3755                 $charset_supersets = array(
3756                         'US-ASCII' => array ('ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4',
3757                                 'ISO-8859-5', 'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8',
3758                                 'ISO-8859-9', 'ISO-8859-10', 'ISO-8859-11', 'ISO-8859-12',
3759                                 'ISO-8859-13', 'ISO-8859-14', 'ISO-8859-15', 'UTF-8',
3760                                 'EUC-JP', 'EUC-', 'EUC-KR', 'EUC-CN')
3761                 );
3762                 if (is_string($validlist))
3763                         $validlist = explode(',', $validlist);
3764                 if (@in_array(strtoupper($encoding), $validlist))
3765                         return true;
3766                 else
3767                 {
3768                         if (array_key_exists($encoding, $charset_supersets))
3769                                 foreach ($validlist as $allowed)
3770                                         if (in_array($allowed, $charset_supersets[$encoding]))
3771                                                 return true;
3772                                 return false;
3773                 }
3774         }
3775
3776 ?>