OSDN Git Service

f04bab53d67377180fc7bea8c6460c6f00a80fcf
[nucleus-jp/nucleus-jp-ancient.git] / utf8 / nucleus / libs / xmlrpc.inc.php
1 <?php
2 // by Edd Dumbill (C) 1999-2002
3 // <edd@usefulinc.com>
4 // $Original: xmlrpc.inc,v 1.158 2007/03/01 21:21:02 ggiunta Exp $
5 // $Id$
6 // $NucleusJP: xmlrpc.inc.php,v 1.6.2.2 2007/09/07 07:04:24 kimitake Exp $
7
8
9 // Copyright (c) 1999,2000,2002 Edd Dumbill.
10 // All rights reserved.
11 //
12 // Redistribution and use in source and binary forms, with or without
13 // modification, are permitted provided that the following conditions
14 // are met:
15 //
16 //    * Redistributions of source code must retain the above copyright
17 //      notice, this list of conditions and the following disclaimer.
18 //
19 //    * Redistributions in binary form must reproduce the above
20 //      copyright notice, this list of conditions and the following
21 //      disclaimer in the documentation and/or other materials provided
22 //      with the distribution.
23 //
24 //    * Neither the name of the "XML-RPC for PHP" nor the names of its
25 //      contributors may be used to endorse or promote products derived
26 //      from this software without specific prior written permission.
27 //
28 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
31 // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
32 // REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
33 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34 // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36 // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37 // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39 // OF THE POSSIBILITY OF SUCH DAMAGE.
40
41         if(!function_exists('xml_parser_create'))
42         {
43                 // For PHP 4 onward, XML functionality is always compiled-in on windows:
44                 // no more need to dl-open it. It might have been compiled out on *nix...
45                 if(strtoupper(substr(PHP_OS, 0, 3) != 'WIN'))
46                 {
47                         dl('xml.so');
48                 }
49         }
50
51         // Try to be backward compat with php < 4.2 (are we not being nice ?)
52         $phpversion = phpversion();
53         if($phpversion[0] == '4' && $phpversion[2] < 2)
54         {
55                 // give an opportunity to user to specify where to include other files from
56                 if(!defined('PHP_XMLRPC_COMPAT_DIR'))
57                 {
58                         define('PHP_XMLRPC_COMPAT_DIR',dirname(__FILE__).'/compat/');
59                 }
60                 if($phpversion[2] == '0')
61                 {
62                         if($phpversion[4] < 6)
63                         {
64                                 include(PHP_XMLRPC_COMPAT_DIR.'is_callable.php');
65                         }
66                         include(PHP_XMLRPC_COMPAT_DIR.'is_scalar.php');
67                         include(PHP_XMLRPC_COMPAT_DIR.'array_key_exists.php');
68                         include(PHP_XMLRPC_COMPAT_DIR.'version_compare.php');
69                 }
70                 include(PHP_XMLRPC_COMPAT_DIR.'var_export.php');
71                 include(PHP_XMLRPC_COMPAT_DIR.'is_a.php');
72         }
73
74         // G. Giunta 2005/01/29: declare global these variables,
75         // so that xmlrpc.inc will work even if included from within a function
76         // Milosch: 2005/08/07 - explicitly request these via $GLOBALS where used.
77         $GLOBALS['xmlrpcI4']='i4';
78         $GLOBALS['xmlrpcInt']='int';
79         $GLOBALS['xmlrpcBoolean']='boolean';
80         $GLOBALS['xmlrpcDouble']='double';
81         $GLOBALS['xmlrpcString']='string';
82         $GLOBALS['xmlrpcDateTime']='dateTime.iso8601';
83         $GLOBALS['xmlrpcBase64']='base64';
84         $GLOBALS['xmlrpcArray']='array';
85         $GLOBALS['xmlrpcStruct']='struct';
86         $GLOBALS['xmlrpcValue']='undefined';
87
88         $GLOBALS['xmlrpcTypes']=array(
89                 $GLOBALS['xmlrpcI4']       => 1,
90                 $GLOBALS['xmlrpcInt']      => 1,
91                 $GLOBALS['xmlrpcBoolean']  => 1,
92                 $GLOBALS['xmlrpcString']   => 1,
93                 $GLOBALS['xmlrpcDouble']   => 1,
94                 $GLOBALS['xmlrpcDateTime'] => 1,
95                 $GLOBALS['xmlrpcBase64']   => 1,
96                 $GLOBALS['xmlrpcArray']    => 2,
97                 $GLOBALS['xmlrpcStruct']   => 3
98         );
99
100         $GLOBALS['xmlrpc_valid_parents'] = array(
101                 'VALUE' => array('MEMBER', 'DATA', 'PARAM', 'FAULT'),
102                 'BOOLEAN' => array('VALUE'),
103                 'I4' => array('VALUE'),
104                 'INT' => array('VALUE'),
105                 'STRING' => array('VALUE'),
106                 'DOUBLE' => array('VALUE'),
107                 'DATETIME.ISO8601' => array('VALUE'),
108                 'BASE64' => array('VALUE'),
109                 'MEMBER' => array('STRUCT'),
110                 'NAME' => array('MEMBER'),
111                 'DATA' => array('ARRAY'),
112                 'ARRAY' => array('VALUE'),
113                 'STRUCT' => array('VALUE'),
114                 'PARAM' => array('PARAMS'),
115                 'METHODNAME' => array('METHODCALL'),
116                 'PARAMS' => array('METHODCALL', 'METHODRESPONSE'),
117                 'FAULT' => array('METHODRESPONSE'),
118                 'NIL' => array('VALUE') // only used when extension activated
119         );
120
121         // define extra types for supporting NULL (useful for json or <NIL/>)
122         $GLOBALS['xmlrpcNull']='null';
123         $GLOBALS['xmlrpcTypes']['null']=1;
124
125         // Not in use anymore since 2.0. Shall we remove it?
126         /// @deprecated
127         $GLOBALS['xmlEntities']=array(
128                 'amp'  => '&',
129                 'quot' => '"',
130                 'lt'   => '<',
131                 'gt'   => '>',
132                 'apos' => "'"
133         );
134
135         // tables used for transcoding different charsets into us-ascii xml
136
137         $GLOBALS['xml_iso88591_Entities']=array();
138         $GLOBALS['xml_iso88591_Entities']['in'] = array();
139         $GLOBALS['xml_iso88591_Entities']['out'] = array();
140         for ($i = 0; $i < 32; $i++)
141         {
142                 $GLOBALS['xml_iso88591_Entities']['in'][] = chr($i);
143                 $GLOBALS['xml_iso88591_Entities']['out'][] = '&#'.$i.';';
144         }
145         for ($i = 160; $i < 256; $i++)
146         {
147                 $GLOBALS['xml_iso88591_Entities']['in'][] = chr($i);
148                 $GLOBALS['xml_iso88591_Entities']['out'][] = '&#'.$i.';';
149         }
150
151         /// @todo add to iso table the characters from cp_1252 range, i.e. 128 to 159.
152         /// These will NOT be present in true ISO-8859-1, but will save the unwary
153         /// windows user from sending junk.
154 /*
155 $cp1252_to_xmlent =
156   array(
157    '\x80'=>'&#x20AC;', '\x81'=>'?', '\x82'=>'&#x201A;', '\x83'=>'&#x0192;',
158    '\x84'=>'&#x201E;', '\x85'=>'&#x2026;', '\x86'=>'&#x2020;', \x87'=>'&#x2021;',
159    '\x88'=>'&#x02C6;', '\x89'=>'&#x2030;', '\x8A'=>'&#x0160;', '\x8B'=>'&#x2039;',
160    '\x8C'=>'&#x0152;', '\x8D'=>'?', '\x8E'=>'&#x017D;', '\x8F'=>'?',
161    '\x90'=>'?', '\x91'=>'&#x2018;', '\x92'=>'&#x2019;', '\x93'=>'&#x201C;',
162    '\x94'=>'&#x201D;', '\x95'=>'&#x2022;', '\x96'=>'&#x2013;', '\x97'=>'&#x2014;',
163    '\x98'=>'&#x02DC;', '\x99'=>'&#x2122;', '\x9A'=>'&#x0161;', '\x9B'=>'&#x203A;',
164    '\x9C'=>'&#x0153;', '\x9D'=>'?', '\x9E'=>'&#x017E;', '\x9F'=>'&#x0178;'
165   );
166 */
167
168         $GLOBALS['xmlrpcerr']['unknown_method']=1;
169         $GLOBALS['xmlrpcstr']['unknown_method']='Unknown method';
170         $GLOBALS['xmlrpcerr']['invalid_return']=2;
171         $GLOBALS['xmlrpcstr']['invalid_return']='Invalid return payload: enable debugging to examine incoming payload';
172         $GLOBALS['xmlrpcerr']['incorrect_params']=3;
173         $GLOBALS['xmlrpcstr']['incorrect_params']='Incorrect parameters passed to method';
174         $GLOBALS['xmlrpcerr']['introspect_unknown']=4;
175         $GLOBALS['xmlrpcstr']['introspect_unknown']="Can't introspect: method unknown";
176         $GLOBALS['xmlrpcerr']['http_error']=5;
177         $GLOBALS['xmlrpcstr']['http_error']="Didn't receive 200 OK from remote server.";
178         $GLOBALS['xmlrpcerr']['no_data']=6;
179         $GLOBALS['xmlrpcstr']['no_data']='No data received from server.';
180         $GLOBALS['xmlrpcerr']['no_ssl']=7;
181         $GLOBALS['xmlrpcstr']['no_ssl']='No SSL support compiled in.';
182         $GLOBALS['xmlrpcerr']['curl_fail']=8;
183         $GLOBALS['xmlrpcstr']['curl_fail']='CURL error';
184         $GLOBALS['xmlrpcerr']['invalid_request']=15;
185         $GLOBALS['xmlrpcstr']['invalid_request']='Invalid request payload';
186         $GLOBALS['xmlrpcerr']['no_curl']=16;
187         $GLOBALS['xmlrpcstr']['no_curl']='No CURL support compiled in.';
188         $GLOBALS['xmlrpcerr']['server_error']=17;
189         $GLOBALS['xmlrpcstr']['server_error']='Internal server error';
190         $GLOBALS['xmlrpcerr']['multicall_error']=18;
191         $GLOBALS['xmlrpcstr']['multicall_error']='Received from server invalid multicall response';
192
193         $GLOBALS['xmlrpcerr']['multicall_notstruct'] = 9;
194         $GLOBALS['xmlrpcstr']['multicall_notstruct'] = 'system.multicall expected struct';
195         $GLOBALS['xmlrpcerr']['multicall_nomethod']  = 10;
196         $GLOBALS['xmlrpcstr']['multicall_nomethod']  = 'missing methodName';
197         $GLOBALS['xmlrpcerr']['multicall_notstring'] = 11;
198         $GLOBALS['xmlrpcstr']['multicall_notstring'] = 'methodName is not a string';
199         $GLOBALS['xmlrpcerr']['multicall_recursion'] = 12;
200         $GLOBALS['xmlrpcstr']['multicall_recursion'] = 'recursive system.multicall forbidden';
201         $GLOBALS['xmlrpcerr']['multicall_noparams']  = 13;
202         $GLOBALS['xmlrpcstr']['multicall_noparams']  = 'missing params';
203         $GLOBALS['xmlrpcerr']['multicall_notarray']  = 14;
204         $GLOBALS['xmlrpcstr']['multicall_notarray']  = 'params is not an array';
205
206         $GLOBALS['xmlrpcerr']['cannot_decompress']=103;
207         $GLOBALS['xmlrpcstr']['cannot_decompress']='Received from server compressed HTTP and cannot decompress';
208         $GLOBALS['xmlrpcerr']['decompress_fail']=104;
209         $GLOBALS['xmlrpcstr']['decompress_fail']='Received from server invalid compressed HTTP';
210         $GLOBALS['xmlrpcerr']['dechunk_fail']=105;
211         $GLOBALS['xmlrpcstr']['dechunk_fail']='Received from server invalid chunked HTTP';
212         $GLOBALS['xmlrpcerr']['server_cannot_decompress']=106;
213         $GLOBALS['xmlrpcstr']['server_cannot_decompress']='Received from client compressed HTTP request and cannot decompress';
214         $GLOBALS['xmlrpcerr']['server_decompress_fail']=107;
215         $GLOBALS['xmlrpcstr']['server_decompress_fail']='Received from client invalid compressed HTTP request';
216
217         // The charset encoding used by the server for received messages and
218         // by the client for received responses when received charset cannot be determined
219         // or is not supported
220         $GLOBALS['xmlrpc_defencoding']='UTF-8';
221
222         // The encoding used internally by PHP.
223         // String values received as xml will be converted to this, and php strings will be converted to xml
224         // as if having been coded with this
225         $GLOBALS['xmlrpc_internalencoding']='ISO-8859-1';
226
227         $GLOBALS['xmlrpcName']='XML-RPC for PHP';
228         $GLOBALS['xmlrpcVersion']='2.2';
229
230         // let user errors start at 800
231         $GLOBALS['xmlrpcerruser']=800;
232         // let XML parse errors start at 100
233         $GLOBALS['xmlrpcerrxml']=100;
234
235         // formulate backslashes for escaping regexp
236         // Not in use anymore since 2.0. Shall we remove it?
237         /// @deprecated
238         $GLOBALS['xmlrpc_backslash']=chr(92).chr(92);
239
240         // set to TRUE to enable correct decoding of <NIL/> values
241         $GLOBALS['xmlrpc_null_extension']=false;
242
243         // used to store state during parsing
244         // quick explanation of components:
245         //   ac - used to accumulate values
246         //   isf - used to indicate a parsing fault (2) or xmlrpcresp fault (1)
247         //   isf_reason - used for storing xmlrpcresp fault string
248         //   lv - used to indicate "looking for a value": implements
249         //        the logic to allow values with no types to be strings
250         //   params - used to store parameters in method calls
251         //   method - used to store method name
252         //   stack - array with genealogy of xml elements names:
253         //           used to validate nesting of xmlrpc elements
254         $GLOBALS['_xh']=null;
255
256         /**
257         * Convert a string to the correct XML representation in a target charset
258         * To help correct communication of non-ascii chars inside strings, regardless
259         * of the charset used when sending requests, parsing them, sending responses
260         * and parsing responses, an option is to convert all non-ascii chars present in the message
261         * into their equivalent 'charset entity'. Charset entities enumerated this way
262         * are independent of the charset encoding used to transmit them, and all XML
263         * parsers are bound to understand them.
264         * Note that in the std case we are not sending a charset encoding mime type
265         * along with http headers, so we are bound by RFC 3023 to emit strict us-ascii.
266         *
267         * @todo do a bit of basic benchmarking (strtr vs. str_replace)
268         * @todo make usage of iconv() or recode_string() or mb_string() where available
269         */
270         function xmlrpc_encode_entitites($data, $src_encoding='', $dest_encoding='')
271         {
272                 if ($src_encoding == '')
273                 {
274                         // lame, but we know no better...
275                         $src_encoding = $GLOBALS['xmlrpc_internalencoding'];
276                 }
277
278                 switch(strtoupper($src_encoding.'_'.$dest_encoding))
279                 {
280                         case 'ISO-8859-1_':
281                         case 'ISO-8859-1_US-ASCII':
282                                 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
283                                 $escaped_data = str_replace($GLOBALS['xml_iso88591_Entities']['in'], $GLOBALS['xml_iso88591_Entities']['out'], $escaped_data);
284                                 break;
285                         case 'ISO-8859-1_UTF-8':
286                                 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
287                                 $escaped_data = utf8_encode($escaped_data);
288                                 break;
289                         case 'ISO-8859-1_ISO-8859-1':
290                         case 'US-ASCII_US-ASCII':
291                         case 'US-ASCII_UTF-8':
292                         case 'US-ASCII_':
293                         case 'US-ASCII_ISO-8859-1':
294                         case 'UTF-8_UTF-8':
295                                 $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
296                                 break;
297                         case 'UTF-8_':
298                         case 'UTF-8_US-ASCII':
299                         case 'UTF-8_ISO-8859-1':
300         // NB: this will choke on invalid UTF-8, going most likely beyond EOF
301         $escaped_data = '';
302         // be kind to users creating string xmlrpcvals out of different php types
303         $data = (string) $data;
304         $ns = strlen ($data);
305         for ($nn = 0; $nn < $ns; $nn++)
306         {
307                 $ch = $data[$nn];
308                 $ii = ord($ch);
309                 //1 7 0bbbbbbb (127)
310                 if ($ii < 128)
311                 {
312                         /// @todo shall we replace this with a (supposedly) faster str_replace?
313                         switch($ii){
314                                 case 34:
315                                         $escaped_data .= '&quot;';
316                                         break;
317                                 case 38:
318                                         $escaped_data .= '&amp;';
319                                         break;
320                                 case 39:
321                                         $escaped_data .= '&apos;';
322                                         break;
323                                 case 60:
324                                         $escaped_data .= '&lt;';
325                                         break;
326                                 case 62:
327                                         $escaped_data .= '&gt;';
328                                         break;
329                                 default:
330                                         $escaped_data .= $ch;
331                         } // switch
332                 }
333                 //2 11 110bbbbb 10bbbbbb (2047)
334                 else if ($ii>>5 == 6)
335                 {
336                         $b1 = ($ii & 31);
337                         $ii = ord($data[$nn+1]);
338                         $b2 = ($ii & 63);
339                         $ii = ($b1 * 64) + $b2;
340                         $ent = sprintf ('&#%d;', $ii);
341                         $escaped_data .= $ent;
342                         $nn += 1;
343                 }
344                 //3 16 1110bbbb 10bbbbbb 10bbbbbb
345                 else if ($ii>>4 == 14)
346                 {
347                         $b1 = ($ii & 31);
348                         $ii = ord($data[$nn+1]);
349                         $b2 = ($ii & 63);
350                         $ii = ord($data[$nn+2]);
351                         $b3 = ($ii & 63);
352                         $ii = ((($b1 * 64) + $b2) * 64) + $b3;
353                         $ent = sprintf ('&#%d;', $ii);
354                         $escaped_data .= $ent;
355                         $nn += 2;
356                 }
357                 //4 21 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
358                 else if ($ii>>3 == 30)
359                 {
360                         $b1 = ($ii & 31);
361                         $ii = ord($data[$nn+1]);
362                         $b2 = ($ii & 63);
363                         $ii = ord($data[$nn+2]);
364                         $b3 = ($ii & 63);
365                         $ii = ord($data[$nn+3]);
366                         $b4 = ($ii & 63);
367                         $ii = ((((($b1 * 64) + $b2) * 64) + $b3) * 64) + $b4;
368                         $ent = sprintf ('&#%d;', $ii);
369                         $escaped_data .= $ent;
370                         $nn += 3;
371                 }
372         }
373                                 break;
374                         default:
375                                 $escaped_data = '';
376                                 error_log("Converting from $src_encoding to $dest_encoding: not supported...");
377                 }
378                 return $escaped_data;
379         }
380
381         /// xml parser handler function for opening element tags
382         function xmlrpc_se($parser, $name, $attrs, $accept_single_vals=false)
383         {
384                 // if invalid xmlrpc already detected, skip all processing
385                 if ($GLOBALS['_xh']['isf'] < 2)
386                 {
387                         // check for correct element nesting
388                         // top level element can only be of 2 types
389                         /// @todo optimization creep: save this check into a bool variable, instead of using count() every time:
390                         ///       there is only a single top level element in xml anyway
391                         if (count($GLOBALS['_xh']['stack']) == 0)
392                         {
393                                 if ($name != 'METHODRESPONSE' && $name != 'METHODCALL' && (
394                                         $name != 'VALUE' && !$accept_single_vals))
395                                 {
396                                         $GLOBALS['_xh']['isf'] = 2;
397                                         $GLOBALS['_xh']['isf_reason'] = 'missing top level xmlrpc element';
398                                         return;
399                                 }
400                                 else
401                                 {
402                                         $GLOBALS['_xh']['rt'] = strtolower($name);
403                                 }
404                         }
405                         else
406                         {
407                                 // not top level element: see if parent is OK
408                                 $parent = end($GLOBALS['_xh']['stack']);
409                                 if (!array_key_exists($name, $GLOBALS['xmlrpc_valid_parents']) || !in_array($parent, $GLOBALS['xmlrpc_valid_parents'][$name]))
410                                 {
411                                         $GLOBALS['_xh']['isf'] = 2;
412                                         $GLOBALS['_xh']['isf_reason'] = "xmlrpc element $name cannot be child of $parent";
413                                         return;
414                                 }
415                         }
416
417                         switch($name)
418                         {
419                                 // optimize for speed switch cases: most common cases first
420                                 case 'VALUE':
421                                         /// @todo we could check for 2 VALUE elements inside a MEMBER or PARAM element
422                                         $GLOBALS['_xh']['vt']='value'; // indicator: no value found yet
423                                         $GLOBALS['_xh']['ac']='';
424                                         $GLOBALS['_xh']['lv']=1;
425                                         $GLOBALS['_xh']['php_class']=null;
426                                         break;
427                                 case 'I4':
428                                 case 'INT':
429                                 case 'STRING':
430                                 case 'BOOLEAN':
431                                 case 'DOUBLE':
432                                 case 'DATETIME.ISO8601':
433                                 case 'BASE64':
434                                         if ($GLOBALS['_xh']['vt']!='value')
435                                         {
436                                                 //two data elements inside a value: an error occurred!
437                                                 $GLOBALS['_xh']['isf'] = 2;
438                                                 $GLOBALS['_xh']['isf_reason'] = "$name element following a {$GLOBALS['_xh']['vt']} element inside a single value";
439                                                 return;
440                                         }
441                                         $GLOBALS['_xh']['ac']=''; // reset the accumulator
442                                         break;
443                                 case 'STRUCT':
444                                 case 'ARRAY':
445                                         if ($GLOBALS['_xh']['vt']!='value')
446                                         {
447                                                 //two data elements inside a value: an error occurred!
448                                                 $GLOBALS['_xh']['isf'] = 2;
449                                                 $GLOBALS['_xh']['isf_reason'] = "$name element following a {$GLOBALS['_xh']['vt']} element inside a single value";
450                                                 return;
451                                         }
452                                         // create an empty array to hold child values, and push it onto appropriate stack
453                                         $cur_val = array();
454                                         $cur_val['values'] = array();
455                                         $cur_val['type'] = $name;
456                                         // check for out-of-band information to rebuild php objs
457                                         // and in case it is found, save it
458                                         if (@isset($attrs['PHP_CLASS']))
459                                         {
460                                                 $cur_val['php_class'] = $attrs['PHP_CLASS'];
461                                         }
462                                         $GLOBALS['_xh']['valuestack'][] = $cur_val;
463                                         $GLOBALS['_xh']['vt']='data'; // be prepared for a data element next
464                                         break;
465                                 case 'DATA':
466                                         if ($GLOBALS['_xh']['vt']!='data')
467                                         {
468                                                 //two data elements inside a value: an error occurred!
469                                                 $GLOBALS['_xh']['isf'] = 2;
470                                                 $GLOBALS['_xh']['isf_reason'] = "found two data elements inside an array element";
471                                                 return;
472                                         }
473                                 case 'METHODCALL':
474                                 case 'METHODRESPONSE':
475                                 case 'PARAMS':
476                                         // valid elements that add little to processing
477                                         break;
478                                 case 'METHODNAME':
479                                 case 'NAME':
480                                         /// @todo we could check for 2 NAME elements inside a MEMBER element
481                                         $GLOBALS['_xh']['ac']='';
482                                         break;
483                                 case 'FAULT':
484                                         $GLOBALS['_xh']['isf']=1;
485                                         break;
486                                 case 'MEMBER':
487                                         $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
488                                         //$GLOBALS['_xh']['ac']='';
489                                         // Drop trough intentionally
490                                 case 'PARAM':
491                                         // clear value type, so we can check later if no value has been passed for this param/member
492                                         $GLOBALS['_xh']['vt']=null;
493                                         break;
494                                 case 'NIL':
495                                         if ($GLOBALS['xmlrpc_null_extension'])
496                                         {
497                                                 if ($GLOBALS['_xh']['vt']!='value')
498                                                 {
499                                                         //two data elements inside a value: an error occurred!
500                                                         $GLOBALS['_xh']['isf'] = 2;
501                                                         $GLOBALS['_xh']['isf_reason'] = "$name element following a {$GLOBALS['_xh']['vt']} element inside a single value";
502                                                         return;
503                                                 }
504                                                 $GLOBALS['_xh']['ac']=''; // reset the accumulator
505                                                 break;
506                                         }
507                                         // we do not support the <NIL/> extension, so
508                                         // drop through intentionally
509                                 default:
510                                         /// INVALID ELEMENT: RAISE ISF so that it is later recognized!!!
511                                         $GLOBALS['_xh']['isf'] = 2;
512                                         $GLOBALS['_xh']['isf_reason'] = "found not-xmlrpc xml element $name";
513                                         break;
514                         }
515
516                         // Save current element name to stack, to validate nesting
517                         $GLOBALS['_xh']['stack'][] = $name;
518
519                         /// @todo optimization creep: move this inside the big switch() above
520                         if($name!='VALUE')
521                         {
522                                 $GLOBALS['_xh']['lv']=0;
523                         }
524                 }
525         }
526
527         /// Used in decoding xml chunks that might represent single xmlrpc values
528         function xmlrpc_se_any($parser, $name, $attrs)
529         {
530                 xmlrpc_se($parser, $name, $attrs, true);
531         }
532
533         /// xml parser handler function for close element tags
534         function xmlrpc_ee($parser, $name, $rebuild_xmlrpcvals = true)
535         {
536                 if ($GLOBALS['_xh']['isf'] < 2)
537                 {
538                         // push this element name from stack
539                         // NB: if XML validates, correct opening/closing is guaranteed and
540                         // we do not have to check for $name == $curr_elem.
541                         // we also checked for proper nesting at start of elements...
542                         $curr_elem = array_pop($GLOBALS['_xh']['stack']);
543
544                         switch($name)
545                         {
546                                 case 'VALUE':
547                                         // This if() detects if no scalar was inside <VALUE></VALUE>
548                                         if ($GLOBALS['_xh']['vt']=='value')
549                                         {
550                                                 $GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac'];
551                                                 $GLOBALS['_xh']['vt']=$GLOBALS['xmlrpcString'];
552                                         }
553
554                                         if ($rebuild_xmlrpcvals)
555                                         {
556                                                 // build the xmlrpc val out of the data received, and substitute it
557                                                 $temp =& new xmlrpcval($GLOBALS['_xh']['value'], $GLOBALS['_xh']['vt']);
558                                                 // in case we got info about underlying php class, save it
559                                                 // in the object we're rebuilding
560                                                 if (isset($GLOBALS['_xh']['php_class']))
561                                                         $temp->_php_class = $GLOBALS['_xh']['php_class'];
562                                                 // check if we are inside an array or struct:
563                                                 // if value just built is inside an array, let's move it into array on the stack
564                                                 $vscount = count($GLOBALS['_xh']['valuestack']);
565                                                 if ($vscount && $GLOBALS['_xh']['valuestack'][$vscount-1]['type']=='ARRAY')
566                                                 {
567                                                         $GLOBALS['_xh']['valuestack'][$vscount-1]['values'][] = $temp;
568                                                 }
569                                                 else
570                                                 {
571                                                         $GLOBALS['_xh']['value'] = $temp;
572                                                 }
573                                         }
574                                         else
575                                         {
576                                                 /// @todo this needs to treat correctly php-serialized objects,
577                                                 /// since std deserializing is done by php_xmlrpc_decode,
578                                                 /// which we will not be calling...
579                                                 if (isset($GLOBALS['_xh']['php_class']))
580                                                 {
581                                                 }
582
583                                                 // check if we are inside an array or struct:
584                                                 // if value just built is inside an array, let's move it into array on the stack
585                                                 $vscount = count($GLOBALS['_xh']['valuestack']);
586                                                 if ($vscount && $GLOBALS['_xh']['valuestack'][$vscount-1]['type']=='ARRAY')
587                                                 {
588                                                         $GLOBALS['_xh']['valuestack'][$vscount-1]['values'][] = $GLOBALS['_xh']['value'];
589                                                 }
590                                         }
591                                         break;
592                                 case 'BOOLEAN':
593                                 case 'I4':
594                                 case 'INT':
595                                 case 'STRING':
596                                 case 'DOUBLE':
597                                 case 'DATETIME.ISO8601':
598                                 case 'BASE64':
599                                         $GLOBALS['_xh']['vt']=strtolower($name);
600                                 /// @todo: optimization creep - remove the if/elseif cycle below
601                     /// since the case() in which we are already did that
602                                         if ($name=='STRING')
603                                         {
604                                                 $GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac'];
605                                         }
606                                         elseif ($name=='DATETIME.ISO8601')
607                                         {
608                                                 if (!preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $GLOBALS['_xh']['ac']))
609                                                 {
610                                                         error_log('XML-RPC: invalid value received in DATETIME: '.$GLOBALS['_xh']['ac']);
611                                                 }
612                                                 $GLOBALS['_xh']['vt']=$GLOBALS['xmlrpcDateTime'];
613                                                 $GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac'];
614                                         }
615                                         elseif ($name=='BASE64')
616                                         {
617                                                 /// @todo check for failure of base64 decoding / catch warnings
618                                                 $GLOBALS['_xh']['value']=base64_decode($GLOBALS['_xh']['ac']);
619                                         }
620                                         elseif ($name=='BOOLEAN')
621                                         {
622                                                 // special case here: we translate boolean 1 or 0 into PHP
623                                                 // constants true or false.
624                                                 // Strings 'true' and 'false' are accepted, even though the
625                                                 // spec never mentions them (see eg. Blogger api docs)
626                                                 // NB: this simple checks helps a lot sanitizing input, ie no
627                                                 // security problems around here
628                                                 if ($GLOBALS['_xh']['ac']=='1' || strcasecmp($GLOBALS['_xh']['ac'], 'true') == 0)
629                                                 {
630                                                         $GLOBALS['_xh']['value']=true;
631                                                 }
632                                                 else
633                                                 {
634                                                         // log if receiveing something strange, even though we set the value to false anyway
635                                                         if ($GLOBALS['_xh']['ac']!='0' && strcasecmp($_xh[$parser]['ac'], 'false') != 0)
636                                                                 error_log('XML-RPC: invalid value received in BOOLEAN: '.$GLOBALS['_xh']['ac']);
637                                                         $GLOBALS['_xh']['value']=false;
638                                                 }
639                                         }
640                                         elseif ($name=='DOUBLE')
641                                         {
642                                                 // we have a DOUBLE
643                                                 // we must check that only 0123456789-.<space> are characters here
644                                                 if (!preg_match('/^[+-]?[eE0123456789 \t.]+$/', $GLOBALS['_xh']['ac']))
645                                                 {
646                                                         /// @todo: find a better way of throwing an error
647                                                         // than this!
648                                                         error_log('XML-RPC: non numeric value received in DOUBLE: '.$GLOBALS['_xh']['ac']);
649                                                         $GLOBALS['_xh']['value']='ERROR_NON_NUMERIC_FOUND';
650                                                 }
651                                                 else
652                                                 {
653                                                         // it's ok, add it on
654                                                         $GLOBALS['_xh']['value']=(double)$GLOBALS['_xh']['ac'];
655                                                 }
656                                         }
657                                         else
658                                         {
659                                                 // we have an I4/INT
660                                                 // we must check that only 0123456789-<space> are characters here
661                                                 if (!preg_match('/^[+-]?[0123456789 \t]+$/', $GLOBALS['_xh']['ac']))
662                                                 {
663                                                         /// @todo find a better way of throwing an error
664                                                         // than this!
665                                                         error_log('XML-RPC: non numeric value received in INT: '.$GLOBALS['_xh']['ac']);
666                                                         $GLOBALS['_xh']['value']='ERROR_NON_NUMERIC_FOUND';
667                                                 }
668                                                 else
669                                                 {
670                                                         // it's ok, add it on
671                                                         $GLOBALS['_xh']['value']=(int)$GLOBALS['_xh']['ac'];
672                                                 }
673                                         }
674                                         //$GLOBALS['_xh']['ac']=''; // is this necessary?
675                                         $GLOBALS['_xh']['lv']=3; // indicate we've found a value
676                                         break;
677                                 case 'NAME':
678                                         $GLOBALS['_xh']['valuestack'][count($GLOBALS['_xh']['valuestack'])-1]['name'] = $GLOBALS['_xh']['ac'];
679                                         break;
680                                 case 'MEMBER':
681                                         //$GLOBALS['_xh']['ac']=''; // is this necessary?
682                                         // add to array in the stack the last element built,
683                                         // unless no VALUE was found
684                                         if ($GLOBALS['_xh']['vt'])
685                                         {
686                                                 $vscount = count($GLOBALS['_xh']['valuestack']);
687                                                 $GLOBALS['_xh']['valuestack'][$vscount-1]['values'][$GLOBALS['_xh']['valuestack'][$vscount-1]['name']] = $GLOBALS['_xh']['value'];
688                                         } else
689                                                 error_log('XML-RPC: missing VALUE inside STRUCT in received xml');
690                                         break;
691                                 case 'DATA':
692                                         //$GLOBALS['_xh']['ac']=''; // is this necessary?
693                                         $GLOBALS['_xh']['vt']=null; // reset this to check for 2 data elements in a row - even if they're empty
694                                         break;
695                                 case 'STRUCT':
696                                 case 'ARRAY':
697                                         // fetch out of stack array of values, and promote it to current value
698                                         $curr_val = array_pop($GLOBALS['_xh']['valuestack']);
699                                         $GLOBALS['_xh']['value'] = $curr_val['values'];
700                                         $GLOBALS['_xh']['vt']=strtolower($name);
701                                         if (isset($curr_val['php_class']))
702                                         {
703                                                 $GLOBALS['_xh']['php_class'] = $curr_val['php_class'];
704                                         }
705                                         break;
706                                 case 'PARAM':
707                                         // add to array of params the current value,
708                                         // unless no VALUE was found
709                                         if ($GLOBALS['_xh']['vt'])
710                                         {
711                                                 $GLOBALS['_xh']['params'][]=$GLOBALS['_xh']['value'];
712                                                 $GLOBALS['_xh']['pt'][]=$GLOBALS['_xh']['vt'];
713                                         }
714                                         else
715                                                 error_log('XML-RPC: missing VALUE inside PARAM in received xml');
716                                         break;
717                                 case 'METHODNAME':
718                                         $GLOBALS['_xh']['method']=preg_replace('/^[\n\r\t ]+/', '', $GLOBALS['_xh']['ac']);
719                                         break;
720                                 case 'NIL':
721                                         if ($GLOBALS['xmlrpc_null_extension'])
722                                         {
723                                                 $GLOBALS['_xh']['vt']='null';
724                                                 $GLOBALS['_xh']['value']=null;
725                                                 $GLOBALS['_xh']['lv']=3;
726                                                 break;
727                                         }
728                                         // drop through intentionally if nil extension not enabled
729                                 case 'PARAMS':
730                                 case 'FAULT':
731                                 case 'METHODCALL':
732                                 case 'METHORESPONSE':
733                                         break;
734                                 default:
735                                         // End of INVALID ELEMENT!
736                                         // shall we add an assert here for unreachable code???
737                                         break;
738                         }
739                 }
740         }
741
742         /// Used in decoding xmlrpc requests/responses without rebuilding xmlrpc values
743         function xmlrpc_ee_fast($parser, $name)
744         {
745                 xmlrpc_ee($parser, $name, false);
746         }
747
748         /// xml parser handler function for character data
749         function xmlrpc_cd($parser, $data)
750         {
751                 // skip processing if xml fault already detected
752                 if ($GLOBALS['_xh']['isf'] < 2)
753                 {
754                         // "lookforvalue==3" means that we've found an entire value
755                         // and should discard any further character data
756                         if($GLOBALS['_xh']['lv']!=3)
757                         {
758                                 // G. Giunta 2006-08-23: useless change of 'lv' from 1 to 2
759                                 //if($GLOBALS['_xh']['lv']==1)
760                                 //{
761                                         // if we've found text and we're just in a <value> then
762                                         // say we've found a value
763                                         //$GLOBALS['_xh']['lv']=2;
764                                 //}
765                                 // we always initialize the accumulator before starting parsing, anyway...
766                                 //if(!@isset($GLOBALS['_xh']['ac']))
767                                 //{
768                                 //      $GLOBALS['_xh']['ac'] = '';
769                                 //}
770                                 $GLOBALS['_xh']['ac'].=$data;
771                         }
772                 }
773         }
774
775         /// xml parser handler function for 'other stuff', ie. not char data or
776         /// element start/end tag. In fact it only gets called on unknown entities...
777         function xmlrpc_dh($parser, $data)
778         {
779                 // skip processing if xml fault already detected
780                 if ($GLOBALS['_xh']['isf'] < 2)
781                 {
782                         if(substr($data, 0, 1) == '&' && substr($data, -1, 1) == ';')
783                         {
784                                 // G. Giunta 2006-08-25: useless change of 'lv' from 1 to 2
785                                 //if($GLOBALS['_xh']['lv']==1)
786                                 //{
787                                 //      $GLOBALS['_xh']['lv']=2;
788                                 //}
789                                 $GLOBALS['_xh']['ac'].=$data;
790                         }
791                 }
792                 return true;
793         }
794
795         class xmlrpc_client
796         {
797                 var $path;
798                 var $server;
799                 var $port=0;
800                 var $method='http';
801                 var $errno;
802                 var $errstr;
803                 var $debug=0;
804                 var $username='';
805                 var $password='';
806                 var $authtype=1;
807                 var $cert='';
808                 var $certpass='';
809                 var $cacert='';
810                 var $cacertdir='';
811                 var $key='';
812                 var $keypass='';
813                 var $verifypeer=true;
814                 var $verifyhost=1;
815                 var $no_multicall=false;
816                 var $proxy='';
817                 var $proxyport=0;
818                 var $proxy_user='';
819                 var $proxy_pass='';
820                 var $proxy_authtype=1;
821                 var $cookies=array();
822                 /**
823                 * List of http compression methods accepted by the client for responses.
824                 * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib
825                 *
826                 * NNB: you can set it to any non-empty array for HTTP11 and HTTPS, since
827                 * in those cases it will be up to CURL to decide the compression methods
828                 * it supports. You might check for the presence of 'zlib' in the output of
829                 * curl_version() to determine wheter compression is supported or not
830                 */
831                 var $accepted_compression = array();
832                 /**
833                 * Name of compression scheme to be used for sending requests.
834                 * Either null, gzip or deflate
835                 */
836                 var $request_compression = '';
837                 /**
838                 * CURL handle: used for keep-alive connections (PHP 4.3.8 up, see:
839                 * http://curl.haxx.se/docs/faq.html#7.3)
840                 */
841                 var $xmlrpc_curl_handle = null;
842                 /// Wheter to use persistent connections for http 1.1 and https
843                 var $keepalive = false;
844                 /// Charset encodings that can be decoded without problems by the client
845                 var $accepted_charset_encodings = array();
846                 /// Charset encoding to be used in serializing request. NULL = use ASCII
847                 var $request_charset_encoding = '';
848                 /**
849                 * Decides the content of xmlrpcresp objects returned by calls to send()
850                 * valid strings are 'xmlrpcvals', 'phpvals' or 'xml'
851                 */
852                 var $return_type = 'xmlrpcvals';
853
854                 /**
855                 * @param string $path either the complete server URL or the PATH part of the xmlrc server URL, e.g. /xmlrpc/server.php
856                 * @param string $server the server name / ip address
857                 * @param integer $port the port the server is listening on, defaults to 80 or 443 depending on protocol used
858                 * @param string $method the http protocol variant: defaults to 'http', 'https' and 'http11' can be used if CURL is installed
859                 */
860                 function xmlrpc_client($path, $server='', $port='', $method='')
861                 {
862                         // allow user to specify all params in $path
863                         if($server == '' and $port == '' and $method == '')
864                         {
865                                 $parts = parse_url($path);
866                                 $server = $parts['host'];
867                                 $path = $parts['path'];
868                                 if(isset($parts['query']))
869                                 {
870                                         $path .= '?'.$parts['query'];
871                                 }
872                                 if(isset($parts['fragment']))
873                                 {
874                                         $path .= '#'.$parts['fragment'];
875                                 }
876                                 if(isset($parts['port']))
877                                 {
878                                         $port = $parts['port'];
879                                 }
880                                 if(isset($parts['scheme']))
881                                 {
882                                         $method = $parts['scheme'];
883                                 }
884                                 if(isset($parts['user']))
885                                 {
886                                         $this->username = $parts['user'];
887                                 }
888                                 if(isset($parts['pass']))
889                                 {
890                                         $this->password = $parts['pass'];
891                                 }
892                         }
893                         if($path == '' || $path[0] != '/')
894                         {
895                                 $this->path='/'.$path;
896                         }
897                         else
898                         {
899                                 $this->path=$path;
900                         }
901                         $this->server=$server;
902                         if($port != '')
903                         {
904                                 $this->port=$port;
905                         }
906                         if($method != '')
907                         {
908                                 $this->method=$method;
909                         }
910
911                         // if ZLIB is enabled, let the client by default accept compressed responses
912                         if(function_exists('gzinflate') || (
913                                 function_exists('curl_init') && (($info = curl_version()) &&
914                                 ((is_string($info) && strpos($info, 'zlib') !== null) || isset($info['libz_version'])))
915                         ))
916                         {
917                                 $this->accepted_compression = array('gzip', 'deflate');
918                         }
919
920                         // keepalives: enabled by default ONLY for PHP >= 4.3.8
921                         // (see http://curl.haxx.se/docs/faq.html#7.3)
922                         if(version_compare(phpversion(), '4.3.8') >= 0)
923                         {
924                                 $this->keepalive = true;
925                         }
926
927                         // by default the xml parser can support these 3 charset encodings
928                         $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII');
929                 }
930
931                 /**
932                 * Enables/disables the echoing to screen of the xmlrpc responses received
933                 * @param integer $debug values 0, 1 and 2 are supported (2 = echo sent msg too, before received response)
934                 * @access public
935                 */
936                 function setDebug($in)
937                 {
938                         $this->debug=$in;
939                 }
940
941                 /**
942                 * Add some http BASIC AUTH credentials, used by the client to authenticate
943                 * @param string $u username
944                 * @param string $p password
945                 * @param integer $t auth type. See curl_setopt man page for supported auth types. Defaults to CURLAUTH_BASIC (basic auth)
946                 * @access public
947                 */
948                 function setCredentials($u, $p, $t=1)
949                 {
950                         $this->username=$u;
951                         $this->password=$p;
952                         $this->authtype=$t;
953                 }
954
955                 /**
956                 * Add a client-side https certificate
957                 * @param string $cert
958                 * @param string $certpass
959                 * @access public
960                 */
961                 function setCertificate($cert, $certpass)
962                 {
963                         $this->cert = $cert;
964                         $this->certpass = $certpass;
965                 }
966
967                 /**
968                 * Add a CA certificate to verify server with (see man page about
969                 * CURLOPT_CAINFO for more details
970                 * @param string $cacert certificate file name (or dir holding certificates)
971                 * @param bool $is_dir set to true to indicate cacert is a dir. defaults to false
972                 * @access public
973                 */
974                 function setCaCertificate($cacert, $is_dir=false)
975                 {
976                         if ($is_dir)
977                         {
978                                 $this->cacert = $cacert;
979                         }
980                         else
981                         {
982                                 $this->cacertdir = $cacert;
983                         }
984                 }
985
986                 /**
987                 * Set attributes for SSL communication: private SSL key
988                 * @param string $key The name of a file containing a private SSL key
989                 * @param string $keypass The secret password needed to use the private SSL key
990                 * @access public
991                 * NB: does not work in older php/curl installs
992                 * Thanks to Daniel Convissor
993                 */
994                 function setKey($key, $keypass)
995                 {
996                         $this->key = $key;
997                         $this->keypass = $keypass;
998                 }
999
1000                 /**
1001                 * Set attributes for SSL communication: verify server certificate
1002                 * @param bool $i enable/disable verification of peer certificate
1003                 * @access public
1004                 */
1005                 function setSSLVerifyPeer($i)
1006                 {
1007                         $this->verifypeer = $i;
1008                 }
1009
1010                 /**
1011                 * Set attributes for SSL communication: verify match of server cert w. hostname
1012                 * @param int $i
1013                 * @access public
1014                 */
1015                 function setSSLVerifyHost($i)
1016                 {
1017                         $this->verifyhost = $i;
1018                 }
1019
1020                 /**
1021                 * Set proxy info
1022                 * @param string $proxyhost
1023                 * @param string $proxyport Defaults to 8080 for HTTP and 443 for HTTPS
1024                 * @param string $proxyusername Leave blank if proxy has public access
1025                 * @param string $proxypassword Leave blank if proxy has public access
1026                 * @param int $proxyauthtype set to constant CURLAUTH_NTLM to use NTLM auth with proxy
1027                 * @access public
1028                 */
1029                 function setProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '', $proxyauthtype = 1)
1030                 {
1031                         $this->proxy = $proxyhost;
1032                         $this->proxyport = $proxyport;
1033                         $this->proxy_user = $proxyusername;
1034                         $this->proxy_pass = $proxypassword;
1035                         $this->proxy_authtype = $proxyauthtype;
1036                 }
1037
1038                 /**
1039                 * Enables/disables reception of compressed xmlrpc responses.
1040                 * Note that enabling reception of compressed responses merely adds some standard
1041                 * http headers to xmlrpc requests. It is up to the xmlrpc server to return
1042                 * compressed responses when receiving such requests.
1043                 * @param string $compmethod either 'gzip', 'deflate', 'any' or ''
1044                 * @access public
1045                 */
1046                 function setAcceptedCompression($compmethod)
1047                 {
1048                         if ($compmethod == 'any')
1049                                 $this->accepted_compression = array('gzip', 'deflate');
1050                         else
1051                                 $this->accepted_compression = array($compmethod);
1052                 }
1053
1054                 /**
1055                 * Enables/disables http compression of xmlrpc request.
1056                 * Take care when sending compressed requests: servers might not support them
1057                 * (and automatic fallback to uncompressed requests is not yet implemented)
1058                 * @param string $compmethod either 'gzip', 'deflate' or ''
1059                 * @access public
1060                 */
1061                 function setRequestCompression($compmethod)
1062                 {
1063                         $this->request_compression = $compmethod;
1064                 }
1065
1066                 /**
1067                 * Adds a cookie to list of cookies that will be sent to server.
1068                 * NB: setting any param but name and value will turn the cookie into a 'version 1' cookie:
1069                 * do not do it unless you know what you are doing
1070                 * @param string $name
1071                 * @param string $value
1072                 * @param string $path
1073                 * @param string $domain
1074                 * @param int $port
1075                 * @access public
1076                 *
1077                 * @todo check correctness of urlencoding cookie value (copied from php way of doing it...)
1078                 */
1079                 function setCookie($name, $value='', $path='', $domain='', $port=null)
1080                 {
1081                         $this->cookies[$name]['value'] = urlencode($value);
1082                         if ($path || $domain || $port)
1083                         {
1084                                 $this->cookies[$name]['path'] = $path;
1085                                 $this->cookies[$name]['domain'] = $domain;
1086                                 $this->cookies[$name]['port'] = $port;
1087                                 $this->cookies[$name]['version'] = 1;
1088                         }
1089                         else
1090                         {
1091                                 $this->cookies[$name]['version'] = 0;
1092                         }
1093                 }
1094
1095                 /**
1096                 * Send an xmlrpc request
1097                 * @param mixed $msg The message object, or an array of messages for using multicall, or the complete xml representation of a request
1098                 * @param integer $timeout Connection timeout, in seconds, If unspecified, a platform specific timeout will apply
1099                 * @param string $method if left unspecified, the http protocol chosen during creation of the object will be used
1100                 * @return xmlrpcresp
1101                 * @access public
1102                 */
1103                 function& send($msg, $timeout=0, $method='')
1104                 {
1105                         // if user deos not specify http protocol, use native method of this client
1106                         // (i.e. method set during call to constructor)
1107                         if($method == '')
1108                         {
1109                                 $method = $this->method;
1110                         }
1111
1112                         if(is_array($msg))
1113                         {
1114                                 // $msg is an array of xmlrpcmsg's
1115                                 $r = $this->multicall($msg, $timeout, $method);
1116                                 return $r;
1117                         }
1118                         elseif(is_string($msg))
1119                         {
1120                                 $n =& new xmlrpcmsg('');
1121                                 $n->payload = $msg;
1122                                 $msg = $n;
1123                         }
1124
1125                         // where msg is an xmlrpcmsg
1126                         $msg->debug=$this->debug;
1127
1128                         if($method == 'https')
1129                         {
1130                                 $r =& $this->sendPayloadHTTPS(
1131                                         $msg,
1132                                         $this->server,
1133                                         $this->port,
1134                                         $timeout,
1135                                         $this->username,
1136                                         $this->password,
1137                                         $this->authtype,
1138                                         $this->cert,
1139                                         $this->certpass,
1140                                         $this->cacert,
1141                                         $this->cacertdir,
1142                                         $this->proxy,
1143                                         $this->proxyport,
1144                                         $this->proxy_user,
1145                                         $this->proxy_pass,
1146                                         $this->proxy_authtype,
1147                                         $this->keepalive,
1148                                         $this->key,
1149                                         $this->keypass
1150                                 );
1151                         }
1152                         elseif($method == 'http11')
1153                         {
1154                                 $r =& $this->sendPayloadCURL(
1155                                         $msg,
1156                                         $this->server,
1157                                         $this->port,
1158                                         $timeout,
1159                                         $this->username,
1160                                         $this->password,
1161                                         $this->authtype,
1162                                         null,
1163                                         null,
1164                                         null,
1165                                         null,
1166                                         $this->proxy,
1167                                         $this->proxyport,
1168                                         $this->proxy_user,
1169                                         $this->proxy_pass,
1170                                         $this->proxy_authtype,
1171                                         'http',
1172                                         $this->keepalive
1173                                 );
1174                         }
1175                         else
1176                         {
1177                                 $r =& $this->sendPayloadHTTP10(
1178                                         $msg,
1179                                         $this->server,
1180                                         $this->port,
1181                                         $timeout,
1182                                         $this->username,
1183                                         $this->password,
1184                                         $this->authtype,
1185                                         $this->proxy,
1186                                         $this->proxyport,
1187                                         $this->proxy_user,
1188                                         $this->proxy_pass,
1189                                         $this->proxy_authtype
1190                                 );
1191                         }
1192
1193                         return $r;
1194                 }
1195
1196                 /**
1197                 * @access private
1198                 */
1199                 function &sendPayloadHTTP10($msg, $server, $port, $timeout=0,
1200                         $username='', $password='', $authtype=1, $proxyhost='',
1201                         $proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1)
1202                 {
1203                         if($port==0)
1204                         {
1205                                 $port=80;
1206                         }
1207
1208                         // Only create the payload if it was not created previously
1209                         if(empty($msg->payload))
1210                         {
1211                                 $msg->createPayload($this->request_charset_encoding);
1212                         }
1213
1214                         $payload = $msg->payload;
1215                         // Deflate request body and set appropriate request headers
1216                         if(function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate'))
1217                         {
1218                                 if($this->request_compression == 'gzip')
1219                                 {
1220                                         $a = @gzencode($payload);
1221                                         if($a)
1222                                         {
1223                                                 $payload = $a;
1224                                                 $encoding_hdr = "Content-Encoding: gzip\r\n";
1225                                         }
1226                                 }
1227                                 else
1228                                 {
1229                                         $a = @gzcompress($payload);
1230                                         if($a)
1231                                         {
1232                                                 $payload = $a;
1233                                                 $encoding_hdr = "Content-Encoding: deflate\r\n";
1234                                         }
1235                                 }
1236                         }
1237                         else
1238                         {
1239                                 $encoding_hdr = '';
1240                         }
1241
1242                         // thanks to Grant Rauscher <grant7@firstworld.net> for this
1243                         $credentials='';
1244                         if($username!='')
1245                         {
1246                                 $credentials='Authorization: Basic ' . base64_encode($username . ':' . $password) . "\r\n";
1247                                 if ($authtype != 1)
1248                                 {
1249                                         error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth is supported with HTTP 1.0');
1250                                 }
1251                         }
1252
1253                         $accepted_encoding = '';
1254                         if(is_array($this->accepted_compression) && count($this->accepted_compression))
1255                         {
1256                                 $accepted_encoding = 'Accept-Encoding: ' . implode(', ', $this->accepted_compression) . "\r\n";
1257                         }
1258
1259                         $proxy_credentials = '';
1260                         if($proxyhost)
1261                         {
1262                                 if($proxyport == 0)
1263                                 {
1264                                         $proxyport = 8080;
1265                                 }
1266                                 $connectserver = $proxyhost;
1267                                 $connectport = $proxyport;
1268                                 $uri = 'http://'.$server.':'.$port.$this->path;
1269                                 if($proxyusername != '')
1270                                 {
1271                                         if ($proxyauthtype != 1)
1272                                         {
1273                                                 error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth to proxy is supported with HTTP 1.0');
1274                                         }
1275                                         $proxy_credentials = 'Proxy-Authorization: Basic ' . base64_encode($proxyusername.':'.$proxypassword) . "\r\n";
1276                                 }
1277                         }
1278                         else
1279                         {
1280                                 $connectserver = $server;
1281                                 $connectport = $port;
1282                                 $uri = $this->path;
1283                         }
1284
1285                         // Cookie generation, as per rfc2965 (version 1 cookies) or
1286                         // netscape's rules (version 0 cookies)
1287                         $cookieheader='';
1288                         foreach ($this->cookies as $name => $cookie)
1289                         {
1290                                 if ($cookie['version'])
1291                                 {
1292                                         $cookieheader .= 'Cookie: $Version="' . $cookie['version'] . '"; ';
1293                                         $cookieheader .= $name . '="' . $cookie['value'] . '";';
1294                                         if ($cookie['path'])
1295                                                 $cookieheader .= ' $Path="' . $cookie['path'] . '";';
1296                                         if ($cookie['domain'])
1297                                                 $cookieheader .= ' $Domain="' . $cookie['domain'] . '";';
1298                                         if ($cookie['port'])
1299                                                 $cookieheader .= ' $Port="' . $cookie['domain'] . '";';
1300                                         $cookieheader = substr($cookieheader, 0, -1) . "\r\n";
1301                                 }
1302                                 else
1303                                 {
1304                                         $cookieheader .= 'Cookie: ' . $name . '=' . $cookie['value'] . "\r\n";
1305                                 }
1306                         }
1307
1308                         $op= 'POST ' . $uri. " HTTP/1.0\r\n" .
1309                                 'User-Agent: ' . $GLOBALS['xmlrpcName'] . ' ' . $GLOBALS['xmlrpcVersion'] . "\r\n" .
1310                                 'Host: '. $server . ':' . $port . "\r\n" .
1311                                 $credentials .
1312                                 $proxy_credentials .
1313                                 $accepted_encoding .
1314                                 $encoding_hdr .
1315                                 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings) . "\r\n" .
1316                                 $cookieheader .
1317                                 'Content-Type: ' . $msg->content_type . "\r\nContent-Length: " .
1318                                 strlen($payload) . "\r\n\r\n" .
1319                                 $payload;
1320
1321                         if($this->debug > 1)
1322                         {
1323                                 print "<PRE>\n---SENDING---\n" . htmlentities($op) . "\n---END---\n</PRE>";
1324                                 // let the client see this now in case http times out...
1325                                 flush();
1326                         }
1327
1328                         if($timeout>0)
1329                         {
1330                                 $fp=@fsockopen($connectserver, $connectport, $this->errno, $this->errstr, $timeout);
1331                         }
1332                         else
1333                         {
1334                                 $fp=@fsockopen($connectserver, $connectport, $this->errno, $this->errstr);
1335                         }
1336                         if($fp)
1337                         {
1338                                 if($timeout>0 && function_exists('stream_set_timeout'))
1339                                 {
1340                                         stream_set_timeout($fp, $timeout);
1341                                 }
1342                         }
1343                         else
1344                         {
1345                                 $this->errstr='Connect error: '.$this->errstr;
1346                                 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $this->errstr . ' (' . $this->errno . ')');
1347                                 return $r;
1348                         }
1349
1350                         if(!fputs($fp, $op, strlen($op)))
1351                         {
1352                                 $this->errstr='Write error';
1353                                 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $this->errstr);
1354                                 return $r;
1355                         }
1356                         else
1357                         {
1358                                 // reset errno and errstr on succesful socket connection
1359                                 $this->errstr = '';
1360                         }
1361                         // G. Giunta 2005/10/24: close socket before parsing.
1362                         // should yeld slightly better execution times, and make easier recursive calls (e.g. to follow http redirects)
1363                         $ipd='';
1364                         while($data=fread($fp, 32768))
1365                         {
1366                                 // shall we check for $data === FALSE?
1367                                 // as per the manual, it signals an error
1368                                 $ipd.=$data;
1369                         }
1370                         fclose($fp);
1371                         $r =& $msg->parseResponse($ipd, false, $this->return_type);
1372                         return $r;
1373
1374                 }
1375
1376                 /**
1377                 * @access private
1378                 */
1379                 function &sendPayloadHTTPS($msg, $server, $port, $timeout=0, $username='',
1380                         $password='', $authtype=1, $cert='',$certpass='', $cacert='', $cacertdir='',
1381                         $proxyhost='', $proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1,
1382                         $keepalive=false, $key='', $keypass='')
1383                 {
1384                         $r =& $this->sendPayloadCURL($msg, $server, $port, $timeout, $username,
1385                                 $password, $authtype, $cert, $certpass, $cacert, $cacertdir, $proxyhost, $proxyport,
1386                                 $proxyusername, $proxypassword, $proxyauthtype, 'https', $keepalive, $key, $keypass);
1387                         return $r;
1388                 }
1389
1390                 /**
1391                 * Contributed by Justin Miller <justin@voxel.net>
1392                 * Requires curl to be built into PHP
1393                 * NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers!
1394                 * @access private
1395                 */
1396                 function &sendPayloadCURL($msg, $server, $port, $timeout=0, $username='',
1397                         $password='', $authtype=1, $cert='', $certpass='', $cacert='', $cacertdir='',
1398                         $proxyhost='', $proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1, $method='https',
1399                         $keepalive=false, $key='', $keypass='')
1400                 {
1401                         if(!function_exists('curl_init'))
1402                         {
1403                                 $this->errstr='CURL unavailable on this install';
1404                                 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_curl'], $GLOBALS['xmlrpcstr']['no_curl']);
1405                                 return $r;
1406                         }
1407                         if($method == 'https')
1408                         {
1409                                 if(($info = curl_version()) &&
1410                                         ((is_string($info) && strpos($info, 'OpenSSL') === null) || (is_array($info) && !isset($info['ssl_version']))))
1411                                 {
1412                                         $this->errstr='SSL unavailable on this install';
1413                                         $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_ssl'], $GLOBALS['xmlrpcstr']['no_ssl']);
1414                                         return $r;
1415                                 }
1416                         }
1417
1418                         if($port == 0)
1419                         {
1420                                 if($method == 'http')
1421                                 {
1422                                         $port = 80;
1423                                 }
1424                                 else
1425                                 {
1426                                         $port = 443;
1427                                 }
1428                         }
1429
1430                         // Only create the payload if it was not created previously
1431                         if(empty($msg->payload))
1432                         {
1433                                 $msg->createPayload($this->request_charset_encoding);
1434                         }
1435
1436                         // Deflate request body and set appropriate request headers
1437                         $payload = $msg->payload;
1438                         if(function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate'))
1439                         {
1440                                 if($this->request_compression == 'gzip')
1441                                 {
1442                                         $a = @gzencode($payload);
1443                                         if($a)
1444                                         {
1445                                                 $payload = $a;
1446                                                 $encoding_hdr = 'Content-Encoding: gzip';
1447                                         }
1448                                 }
1449                                 else
1450                                 {
1451                                         $a = @gzcompress($payload);
1452                                         if($a)
1453                                         {
1454                                                 $payload = $a;
1455                                                 $encoding_hdr = 'Content-Encoding: deflate';
1456                                         }
1457                                 }
1458                         }
1459                         else
1460                         {
1461                                 $encoding_hdr = '';
1462                         }
1463
1464                         if($this->debug > 1)
1465                         {
1466                                 print "<PRE>\n---SENDING---\n" . htmlentities($payload) . "\n---END---\n</PRE>";
1467                                 // let the client see this now in case http times out...
1468                                 flush();
1469                         }
1470
1471                         if(!$keepalive || !$this->xmlrpc_curl_handle)
1472                         {
1473                                 $curl = curl_init($method . '://' . $server . ':' . $port . $this->path);
1474                                 if($keepalive)
1475                                 {
1476                                         $this->xmlrpc_curl_handle = $curl;
1477                                 }
1478                         }
1479                         else
1480                         {
1481                                 $curl = $this->xmlrpc_curl_handle;
1482                         }
1483
1484                         // results into variable
1485                         curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
1486
1487                         if($this->debug)
1488                         {
1489                                 curl_setopt($curl, CURLOPT_VERBOSE, 1);
1490                         }
1491                         curl_setopt($curl, CURLOPT_USERAGENT, $GLOBALS['xmlrpcName'].' '.$GLOBALS['xmlrpcVersion']);
1492                         // required for XMLRPC: post the data
1493                         curl_setopt($curl, CURLOPT_POST, 1);
1494                         // the data
1495                         curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
1496
1497                         // return the header too
1498                         curl_setopt($curl, CURLOPT_HEADER, 1);
1499
1500                         // will only work with PHP >= 5.0
1501                         // NB: if we set an empty string, CURL will add http header indicating
1502                         // ALL methods it is supporting. This is possibly a better option than
1503                         // letting the user tell what curl can / cannot do...
1504                         if(is_array($this->accepted_compression) && count($this->accepted_compression))
1505                         {
1506                                 //curl_setopt($curl, CURLOPT_ENCODING, implode(',', $this->accepted_compression));
1507                                 // empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1508                                 if (count($this->accepted_compression) == 1)
1509                                 {
1510                                         curl_setopt($curl, CURLOPT_ENCODING, $this->accepted_compression[0]);
1511                                 }
1512                                 else
1513                                         curl_setopt($curl, CURLOPT_ENCODING, '');
1514                         }
1515                         // extra headers
1516                         $headers = array('Content-Type: ' . $msg->content_type , 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings));
1517                         // if no keepalive is wanted, let the server know it in advance
1518                         if(!$keepalive)
1519                         {
1520                                 $headers[] = 'Connection: close';
1521                         }
1522                         // request compression header
1523                         if($encoding_hdr)
1524                         {
1525                                 $headers[] = $encoding_hdr;
1526                         }
1527
1528                         curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
1529                         // timeout is borked
1530                         if($timeout)
1531                         {
1532                                 curl_setopt($curl, CURLOPT_TIMEOUT, $timeout == 1 ? 1 : $timeout - 1);
1533                         }
1534
1535                         if($username && $password)
1536                         {
1537                                 curl_setopt($curl, CURLOPT_USERPWD, $username.':'.$password);
1538                                 if (defined('CURLOPT_HTTPAUTH'))
1539                                 {
1540                                         curl_setopt($curl, CURLOPT_HTTPAUTH, $authtype);
1541                                 }
1542                                 else if ($authtype != 1)
1543                                 {
1544                                         error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth is supported by the current PHP/curl install');
1545                                 }
1546                         }
1547
1548                         if($method == 'https')
1549                         {
1550                                 // set cert file
1551                                 if($cert)
1552                                 {
1553                                         curl_setopt($curl, CURLOPT_SSLCERT, $cert);
1554                                 }
1555                                 // set cert password
1556                                 if($certpass)
1557                                 {
1558                                         curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $certpass);
1559                                 }
1560                                 // whether to verify remote host's cert
1561                                 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verifypeer);
1562                                 // set ca certificates file/dir
1563                                 if($cacert)
1564                                 {
1565                                         curl_setopt($curl, CURLOPT_CAINFO, $cacert);
1566                                 }
1567                                 if($cacertdir)
1568                                 {
1569                                         curl_setopt($curl, CURLOPT_CAPATH, $cacertdir);
1570                                 }
1571                                 // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1572                                 if($key)
1573                                 {
1574                                         curl_setopt($curl, CURLOPT_SSLKEY, $key);
1575                                 }
1576                                 // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1577                                 if($keypass)
1578                                 {
1579                                         curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $keypass);
1580                                 }
1581                                 // 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
1582                                 curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $this->verifyhost);
1583                         }
1584
1585                         // proxy info
1586                         if($proxyhost)
1587                         {
1588                                 if($proxyport == 0)
1589                                 {
1590                                         $proxyport = 8080; // NB: even for HTTPS, local connection is on port 8080
1591                                 }
1592                                 curl_setopt($curl, CURLOPT_PROXY,$proxyhost.':'.$proxyport);
1593                                 //curl_setopt($curl, CURLOPT_PROXYPORT,$proxyport);
1594                                 if($proxyusername)
1595                                 {
1596                                         curl_setopt($curl, CURLOPT_PROXYUSERPWD, $proxyusername.':'.$proxypassword);
1597                                         if (defined('CURLOPT_PROXYAUTH'))
1598                                         {
1599                                                 curl_setopt($curl, CURLOPT_PROXYAUTH, $proxyauthtype);
1600                                         }
1601                                         else if ($proxyauthtype != 1)
1602                                         {
1603                                                 error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth to proxy is supported by the current PHP/curl install');
1604                                         }
1605                                 }
1606                         }
1607
1608                         // NB: should we build cookie http headers by hand rather than let CURL do it?
1609                         // the following code does not honour 'expires', 'path' and 'domain' cookie attributes
1610                         // set to clint obj the the user...
1611                         if (count($this->cookies))
1612                         {
1613                                 $cookieheader = '';
1614                                 foreach ($this->cookies as $name => $cookie)
1615                                 {
1616                                         $cookieheader .= $name . '=' . $cookie['value'] . ', ';
1617                                 }
1618                                 curl_setopt($curl, CURLOPT_COOKIE, substr($cookieheader, 0, -2));
1619                         }
1620
1621                         $result = curl_exec($curl);
1622
1623                         if(!$result)
1624                         {
1625                                 $this->errstr='no response';
1626                                 $resp=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['curl_fail'], $GLOBALS['xmlrpcstr']['curl_fail']. ': '. curl_error($curl));
1627                                 if(!$keepalive)
1628                                 {
1629                                         curl_close($curl);
1630                                 }
1631                         }
1632                         else
1633                         {
1634                                 if(!$keepalive)
1635                                 {
1636                                         curl_close($curl);
1637                                 }
1638                                 $resp =& $msg->parseResponse($result, true, $this->return_type);
1639                         }
1640                         return $resp;
1641                 }
1642
1643                 /**
1644                 * Send an array of request messages and return an array of responses.
1645                 * Unless $this->no_multicall has been set to true, it will try first
1646                 * to use one single xmlrpc call to server method system.multicall, and
1647                 * revert to sending many successive calls in case of failure.
1648                 * This failure is also stored in $this->no_multicall for subsequent calls.
1649                 * Unfortunately, there is no server error code universally used to denote
1650                 * the fact that multicall is unsupported, so there is no way to reliably
1651                 * distinguish between that and a temporary failure.
1652                 * If you are sure that server supports multicall and do not want to
1653                 * fallback to using many single calls, set the fourth parameter to FALSE.
1654                 *
1655                 * NB: trying to shoehorn extra functionality into existing syntax has resulted
1656                 * in pretty much convoluted code...
1657                 *
1658                 * @param array $msgs an array of xmlrpcmsg objects
1659                 * @param integer $timeout connection timeout (in seconds)
1660                 * @param string $method the http protocol variant to be used
1661                 * @param boolean fallback When true, upon receiveing an error during multicall, multiple single calls will be attempted
1662                 * @return array
1663                 * @access public
1664                 */
1665                 function multicall($msgs, $timeout=0, $method='', $fallback=true)
1666                 {
1667                         if ($method == '')
1668                         {
1669                                 $method = $this->method;
1670                         }
1671                         if(!$this->no_multicall)
1672                         {
1673                                 $results = $this->_try_multicall($msgs, $timeout, $method);
1674                                 if(is_array($results))
1675                                 {
1676                                         // System.multicall succeeded
1677                                         return $results;
1678                                 }
1679                                 else
1680                                 {
1681                                         // either system.multicall is unsupported by server,
1682                                         // or call failed for some other reason.
1683                                         if ($fallback)
1684                                         {
1685                                                 // Don't try it next time...
1686                                                 $this->no_multicall = true;
1687                                         }
1688                                         else
1689                                         {
1690                                                 if (is_a($results, 'xmlrpcresp'))
1691                                                 {
1692                                                         $result = $results;
1693                                                 }
1694                                                 else
1695                                                 {
1696                                                         $result =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['multicall_error'], $GLOBALS['xmlrpcstr']['multicall_error']);
1697                                                 }
1698                                         }
1699                                 }
1700                         }
1701                         else
1702                         {
1703                                 // override fallback, in case careless user tries to do two
1704                                 // opposite things at the same time
1705                                 $fallback = true;
1706                         }
1707
1708                         $results = array();
1709                         if ($fallback)
1710                         {
1711                                 // system.multicall is (probably) unsupported by server:
1712                                 // emulate multicall via multiple requests
1713                                 foreach($msgs as $msg)
1714                                 {
1715                                         $results[] =& $this->send($msg, $timeout, $method);
1716                                 }
1717                         }
1718                         else
1719                         {
1720                                 // user does NOT want to fallback on many single calls:
1721                                 // since we should always return an array of responses,
1722                                 // return an array with the same error repeated n times
1723                                 foreach($msgs as $msg)
1724                                 {
1725                                         $results[] = $result;
1726                                 }
1727                         }
1728                         return $results;
1729                 }
1730
1731                 /**
1732                 * Attempt to boxcar $msgs via system.multicall.
1733                 * Returns either an array of xmlrpcreponses, an xmlrpc error response
1734                 * or false (when received response does not respect valid multicall syntax)
1735                 * @access private
1736                 */
1737                 function _try_multicall($msgs, $timeout, $method)
1738                 {
1739                         // Construct multicall message
1740                         $calls = array();
1741                         foreach($msgs as $msg)
1742                         {
1743                                 $call['methodName'] =& new xmlrpcval($msg->method(),'string');
1744                                 $numParams = $msg->getNumParams();
1745                                 $params = array();
1746                                 for($i = 0; $i < $numParams; $i++)
1747                                 {
1748                                         $params[$i] = $msg->getParam($i);
1749                                 }
1750                                 $call['params'] =& new xmlrpcval($params, 'array');
1751                                 $calls[] =& new xmlrpcval($call, 'struct');
1752                         }
1753                         $multicall =& new xmlrpcmsg('system.multicall');
1754                         $multicall->addParam(new xmlrpcval($calls, 'array'));
1755
1756                         // Attempt RPC call
1757                         $result =& $this->send($multicall, $timeout, $method);
1758
1759                         if($result->faultCode() != 0)
1760                         {
1761                                 // call to system.multicall failed
1762                                 return $result;
1763                         }
1764
1765                         // Unpack responses.
1766                         $rets = $result->value();
1767
1768                         if ($this->return_type == 'xml')
1769                         {
1770                                         return $rets;
1771                         }
1772                         else if ($this->return_type == 'phpvals')
1773                         {
1774                                 ///@todo test this code branch...
1775                                 $rets = $result->value();
1776                                 if(!is_array($rets))
1777                                 {
1778                                         return false;           // bad return type from system.multicall
1779                                 }
1780                                 $numRets = count($rets);
1781                                 if($numRets != count($msgs))
1782                                 {
1783                                         return false;           // wrong number of return values.
1784                                 }
1785
1786                                 $response = array();
1787                                 for($i = 0; $i < $numRets; $i++)
1788                                 {
1789                                         $val = $rets[$i];
1790                                         if (!is_array($val)) {
1791                                                 return false;
1792                                         }
1793                                         switch(count($val))
1794                                         {
1795                                                 case 1:
1796                                                         if(!isset($val[0]))
1797                                                         {
1798                                                                 return false;           // Bad value
1799                                                         }
1800                                                         // Normal return value
1801                                                         $response[$i] =& new xmlrpcresp($val[0], 0, '', 'phpvals');
1802                                                         break;
1803                                                 case 2:
1804                                                         ///     @todo remove usage of @: it is apparently quite slow
1805                                                         $code = @$val['faultCode'];
1806                                                         if(!is_int($code))
1807                                                         {
1808                                                                 return false;
1809                                                         }
1810                                                         $str = @$val['faultString'];
1811                                                         if(!is_string($str))
1812                                                         {
1813                                                                 return false;
1814                                                         }
1815                                                         $response[$i] =& new xmlrpcresp(0, $code, $str);
1816                                                         break;
1817                                                 default:
1818                                                         return false;
1819                                         }
1820                                 }
1821                                 return $response;
1822                         }
1823                         else // return type == 'xmlrpcvals'
1824                         {
1825                                 $rets = $result->value();
1826                                 if($rets->kindOf() != 'array')
1827                                 {
1828                                         return false;           // bad return type from system.multicall
1829                                 }
1830                                 $numRets = $rets->arraysize();
1831                                 if($numRets != count($msgs))
1832                                 {
1833                                         return false;           // wrong number of return values.
1834                                 }
1835
1836                                 $response = array();
1837                                 for($i = 0; $i < $numRets; $i++)
1838                                 {
1839                                         $val = $rets->arraymem($i);
1840                                         switch($val->kindOf())
1841                                         {
1842                                                 case 'array':
1843                                                         if($val->arraysize() != 1)
1844                                                         {
1845                                                                 return false;           // Bad value
1846                                                         }
1847                                                         // Normal return value
1848                                                         $response[$i] =& new xmlrpcresp($val->arraymem(0));
1849                                                         break;
1850                                                 case 'struct':
1851                                                         $code = $val->structmem('faultCode');
1852                                                         if($code->kindOf() != 'scalar' || $code->scalartyp() != 'int')
1853                                                         {
1854                                                                 return false;
1855                                                         }
1856                                                         $str = $val->structmem('faultString');
1857                                                         if($str->kindOf() != 'scalar' || $str->scalartyp() != 'string')
1858                                                         {
1859                                                                 return false;
1860                                                         }
1861                                                         $response[$i] =& new xmlrpcresp(0, $code->scalarval(), $str->scalarval());
1862                                                         break;
1863                                                 default:
1864                                                         return false;
1865                                         }
1866                                 }
1867                                 return $response;
1868                         }
1869                 }
1870         } // end class xmlrpc_client
1871
1872         class xmlrpcresp
1873         {
1874                 var $val = 0;
1875                 var $valtyp;
1876                 var $errno = 0;
1877                 var $errstr = '';
1878                 var $payload;
1879                 var $hdrs = array();
1880                 var $_cookies = array();
1881                 var $content_type = 'text/xml';
1882                 var $raw_data = '';
1883
1884                 /**
1885                 * @param mixed $val either an xmlrpcval obj, a php value or the xml serialization of an xmlrpcval (a string)
1886                 * @param integer $fcode set it to anything but 0 to create an error response
1887                 * @param string $fstr the error string, in case of an error response
1888                 * @param string $valtyp either 'xmlrpcvals', 'phpvals' or 'xml'
1889                 *
1890                 * @todo add check that $val / $fcode / $fstr is of correct type???
1891                 * NB: as of now we do not do it, since it might be either an xmlrpcval or a plain
1892                 * php val, or a complete xml chunk, depending on usage of xmlrpc_client::send() inside which creator is called...
1893                 */
1894                 function xmlrpcresp($val, $fcode = 0, $fstr = '', $valtyp='')
1895                 {
1896                         if($fcode != 0)
1897                         {
1898                                 // error response
1899                                 $this->errno = $fcode;
1900                                 $this->errstr = $fstr;
1901                                 //$this->errstr = htmlspecialchars($fstr); // XXX: encoding probably shouldn't be done here; fix later.
1902                         }
1903                         else
1904                         {
1905                                 // successful response
1906                                 $this->val = $val;
1907                                 if ($valtyp == '')
1908                                 {
1909                                         // user did not declare type of response value: try to guess it
1910                                         if (is_object($this->val) && is_a($this->val, 'xmlrpcval'))
1911                                         {
1912                                                 $this->valtyp = 'xmlrpcvals';
1913                                         }
1914                                         else if (is_string($this->val))
1915                                         {
1916                                                 $this->valtyp = 'xml';
1917
1918                                         }
1919                                         else
1920                                         {
1921                                                 $this->valtyp = 'phpvals';
1922                                         }
1923                                 }
1924                                 else
1925                                 {
1926                                         // user declares type of resp value: believe him
1927                                         $this->valtyp = $valtyp;
1928                                 }
1929                         }
1930                 }
1931
1932                 /**
1933                 * Returns the error code of the response.
1934                 * @return integer the error code of this response (0 for not-error responses)
1935                 * @access public
1936                 */
1937                 function faultCode()
1938                 {
1939                         return $this->errno;
1940                 }
1941
1942                 /**
1943                 * Returns the error code of the response.
1944                 * @return string the error string of this response ('' for not-error responses)
1945                 * @access public
1946                 */
1947                 function faultString()
1948                 {
1949                         return $this->errstr;
1950                 }
1951
1952                 /**
1953                 * Returns the value received by the server.
1954                 * @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
1955                 * @access public
1956                 */
1957                 function value()
1958                 {
1959                         return $this->val;
1960                 }
1961
1962                 /**
1963                 * Returns an array with the cookies received from the server.
1964                 * Array has the form: $cookiename => array ('value' => $val, $attr1 => $val1, $attr2 = $val2, ...)
1965                 * with attributes being e.g. 'expires', 'path', domain'.
1966                 * NB: cookies sent as 'expired' by the server (i.e. with an expiry date in the past)
1967                 * are still present in the array. It is up to the user-defined code to decide
1968                 * how to use the received cookies, and wheter they have to be sent back with the next
1969                 * request to the server (using xmlrpc_client::setCookie) or not
1970                 * @return array array of cookies received from the server
1971                 * @access public
1972                 */
1973                 function cookies()
1974                 {
1975                         return $this->_cookies;
1976                 }
1977
1978                 /**
1979                 * Returns xml representation of the response. XML prologue not included
1980                 * @param string $charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed
1981                 * @return string the xml representation of the response
1982                 * @access public
1983                 */
1984                 function serialize($charset_encoding='')
1985                 {
1986                         if ($charset_encoding != '')
1987                                 $this->content_type = 'text/xml; charset=' . $charset_encoding;
1988                         else
1989                                 $this->content_type = 'text/xml';
1990                         $result = "<methodResponse>\n";
1991                         if($this->errno)
1992                         {
1993                                 // G. Giunta 2005/2/13: let non-ASCII response messages be tolerated by clients
1994                                 // by xml-encoding non ascii chars
1995                                 $result .= "<fault>\n" .
1996 "<value>\n<struct><member><name>faultCode</name>\n<value><int>" . $this->errno .
1997 "</int></value>\n</member>\n<member>\n<name>faultString</name>\n<value><string>" .
1998 xmlrpc_encode_entitites($this->errstr, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding) . "</string></value>\n</member>\n" .
1999 "</struct>\n</value>\n</fault>";
2000                         }
2001                         else
2002                         {
2003                                 if(!is_object($this->val) || !is_a($this->val, 'xmlrpcval'))
2004                                 {
2005                                         if (is_string($this->val) && $this->valtyp == 'xml')
2006                                         {
2007                                                 $result .= "<params>\n<param>\n" .
2008                                                         $this->val .
2009                                                         "</param>\n</params>";
2010                                         }
2011                                         else
2012                                         {
2013                                                 /// @todo try to build something serializable?
2014                                                 die('cannot serialize xmlrpcresp objects whose content is native php values');
2015                                         }
2016                                 }
2017                                 else
2018                                 {
2019                                         $result .= "<params>\n<param>\n" .
2020                                                 $this->val->serialize($charset_encoding) .
2021                                                 "</param>\n</params>";
2022                                 }
2023                         }
2024                         $result .= "\n</methodResponse>";
2025                         $this->payload = $result;
2026                         return $result;
2027                 }
2028         }
2029
2030         class xmlrpcmsg
2031         {
2032                 var $payload;
2033                 var $methodname;
2034                 var $params=array();
2035                 var $debug=0;
2036                 var $content_type = 'text/xml';
2037
2038                 /**
2039                 * @param string $meth the name of the method to invoke
2040                 * @param array $pars array of parameters to be paased to the method (xmlrpcval objects)
2041                 */
2042                 function xmlrpcmsg($meth, $pars=0)
2043                 {
2044                         $this->methodname=$meth;
2045                         if(is_array($pars) && count($pars)>0)
2046                         {
2047                                 for($i=0; $i<count($pars); $i++)
2048                                 {
2049                                         $this->addParam($pars[$i]);
2050                                 }
2051                         }
2052                 }
2053
2054                 /**
2055                 * @access private
2056                 */
2057                 function xml_header($charset_encoding='')
2058                 {
2059                         if ($charset_encoding != '')
2060                         {
2061                                 return "<?xml version=\"1.0\" encoding=\"$charset_encoding\" ?" . ">\n<methodCall>\n";
2062                         }
2063                         else
2064                         {
2065                                 return "<?xml version=\"1.0\"?" . ">\n<methodCall>\n";
2066                         }
2067                 }
2068
2069                 /**
2070                 * @access private
2071                 */
2072                 function xml_footer()
2073                 {
2074                         return '</methodCall>';
2075                 }
2076
2077                 /**
2078                 * @access private
2079                 */
2080                 function kindOf()
2081                 {
2082                         return 'msg';
2083                 }
2084
2085                 /**
2086                 * @access private
2087                 */
2088                 function createPayload($charset_encoding='')
2089                 {
2090                         if ($charset_encoding != '')
2091                                 $this->content_type = 'text/xml; charset=' . $charset_encoding;
2092                         else
2093                                 $this->content_type = 'text/xml';
2094                         $this->payload=$this->xml_header($charset_encoding);
2095                         $this->payload.='<methodName>' . $this->methodname . "</methodName>\n";
2096                         $this->payload.="<params>\n";
2097                         for($i=0; $i<count($this->params); $i++)
2098                         {
2099                                 $p=$this->params[$i];
2100                                 $this->payload.="<param>\n" . $p->serialize($charset_encoding) .
2101                                 "</param>\n";
2102                         }
2103                         $this->payload.="</params>\n";
2104                         $this->payload.=$this->xml_footer();
2105                 }
2106
2107                 /**
2108                 * Gets/sets the xmlrpc method to be invoked
2109                 * @param string $meth the method to be set (leave empty not to set it)
2110                 * @return string the method that will be invoked
2111                 * @access public
2112                 */
2113                 function method($meth='')
2114                 {
2115                         if($meth!='')
2116                         {
2117                                 $this->methodname=$meth;
2118                         }
2119                         return $this->methodname;
2120                 }
2121
2122                 /**
2123                 * Returns xml representation of the message. XML prologue included
2124                 * @return string the xml representation of the message, xml prologue included
2125                 * @access public
2126                 */
2127                 function serialize($charset_encoding='')
2128                 {
2129                         $this->createPayload($charset_encoding);
2130                         return $this->payload;
2131                 }
2132
2133                 /**
2134                 * Add a parameter to the list of parameters to be used upon method invocation
2135                 * @param xmlrpcval $par
2136                 * @return boolean false on failure
2137                 * @access public
2138                 */
2139                 function addParam($par)
2140                 {
2141                         // add check: do not add to self params which are not xmlrpcvals
2142                         if(is_object($par) && is_a($par, 'xmlrpcval'))
2143                         {
2144                                 $this->params[]=$par;
2145                                 return true;
2146                         }
2147                         else
2148                         {
2149                                 return false;
2150                         }
2151                 }
2152
2153                 /**
2154                 * Returns the nth parameter in the message. The index zero-based.
2155                 * @param integer $i the index of the parameter to fetch (zero based)
2156                 * @return xmlrpcval the i-th parameter
2157                 * @access public
2158                 */
2159                 function getParam($i) { return $this->params[$i]; }
2160
2161                 /**
2162                 * Returns the number of parameters in the messge.
2163                 * @return integer the number of parameters currently set
2164                 * @access public
2165                 */
2166                 function getNumParams() { return count($this->params); }
2167
2168                 /**
2169                 * Given an open file handle, read all data available and parse it as axmlrpc response.
2170                 * NB: the file handle is not closed by this function.
2171                 * @access public
2172                 * @return xmlrpcresp
2173                 * @todo add 2nd & 3rd param to be passed to ParseResponse() ???
2174                 */
2175                 function &parseResponseFile($fp)
2176                 {
2177                         $ipd='';
2178                         while($data=fread($fp, 32768))
2179                         {
2180                                 $ipd.=$data;
2181                         }
2182                         //fclose($fp);
2183                         $r =& $this->parseResponse($ipd);
2184                         return $r;
2185                 }
2186
2187                 /**
2188                 * Parses HTTP headers and separates them from data.
2189                 * @access private
2190                 */
2191                 function &parseResponseHeaders(&$data, $headers_processed=false)
2192                 {
2193                                 // Support "web-proxy-tunelling" connections for https through proxies
2194                                 if(preg_match('/^HTTP\/1\.[0-1] 200 Connection established/', $data))
2195                                 {
2196                                         // Look for CR/LF or simple LF as line separator,
2197                                         // (even though it is not valid http)
2198                                         $pos = strpos($data,"\r\n\r\n");
2199                                         if($pos || is_int($pos))
2200                                         {
2201                                                 $bd = $pos+4;
2202                                         }
2203                                         else
2204                                         {
2205                                                 $pos = strpos($data,"\n\n");
2206                                                 if($pos || is_int($pos))
2207                                                 {
2208                                                         $bd = $pos+2;
2209                                                 }
2210                                                 else
2211                                                 {
2212                                                         // No separation between response headers and body: fault?
2213                                                         $bd = 0;
2214                                                 }
2215                                         }
2216                                         if ($bd)
2217                                         {
2218                                                 // this filters out all http headers from proxy.
2219                                                 // maybe we could take them into account, too?
2220                                                 $data = substr($data, $bd);
2221                                         }
2222                                         else
2223                                         {
2224                                                 error_log('XML-RPC: xmlrpcmsg::parseResponse: HTTPS via proxy error, tunnel connection possibly failed');
2225                                                 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $GLOBALS['xmlrpcstr']['http_error']. ' (HTTPS via proxy error, tunnel connection possibly failed)');
2226                                                 return $r;
2227                                         }
2228                                 }
2229
2230                                 // Strip HTTP 1.1 100 Continue header if present
2231                                 while(preg_match('/^HTTP\/1\.1 1[0-9]{2} /', $data))
2232                                 {
2233                                         $pos = strpos($data, 'HTTP', 12);
2234                                         // server sent a Continue header without any (valid) content following...
2235                                         // give the client a chance to know it
2236                                         if(!$pos && !is_int($pos)) // works fine in php 3, 4 and 5
2237                                         {
2238                                                 break;
2239                                         }
2240                                         $data = substr($data, $pos);
2241                                 }
2242                                 if(!preg_match('/^HTTP\/[0-9.]+ 200 /', $data))
2243                                 {
2244                                         $errstr= substr($data, 0, strpos($data, "\n")-1);
2245                                         error_log('XML-RPC: xmlrpcmsg::parseResponse: HTTP error, got response: ' .$errstr);
2246                                         $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $GLOBALS['xmlrpcstr']['http_error']. ' (' . $errstr . ')');
2247                                         return $r;
2248                                 }
2249
2250                                 $GLOBALS['_xh']['headers'] = array();
2251                                 $GLOBALS['_xh']['cookies'] = array();
2252
2253                                 // be tolerant to usage of \n instead of \r\n to separate headers and data
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                                                 // we could take some action here instead of going on...
2271                                                 $bd = 0;
2272                                         }
2273                                 }
2274                                 // be tolerant to line endings, and extra empty lines
2275                                 $ar = split("\r?\n", trim(substr($data, 0, $pos)));
2276                                 while(list(,$line) = @each($ar))
2277                                 {
2278                                         // take care of multi-line headers and cookies
2279                                         $arr = explode(':',$line,2);
2280                                         if(count($arr) > 1)
2281                                         {
2282                                                 $header_name = strtolower(trim($arr[0]));
2283                                                 /// @todo some other headers (the ones that allow a CSV list of values)
2284                                                 /// do allow many values to be passed using multiple header lines.
2285                                                 /// We should add content to $GLOBALS['_xh']['headers'][$header_name]
2286                                                 /// instead of replacing it for those...
2287                                                 if ($header_name == 'set-cookie' || $header_name == 'set-cookie2')
2288                                                 {
2289                                                         if ($header_name == 'set-cookie2')
2290                                                         {
2291                                                                 // version 2 cookies:
2292                                                                 // there could be many cookies on one line, comma separated
2293                                                                 $cookies = explode(',', $arr[1]);
2294                                                         }
2295                                                         else
2296                                                         {
2297                                                                 $cookies = array($arr[1]);
2298                                                         }
2299                                                         foreach ($cookies as $cookie)
2300                                                         {
2301                                                                 // glue together all received cookies, using a comma to separate them
2302                                                                 // (same as php does with getallheaders())
2303                                                                 if (isset($GLOBALS['_xh']['headers'][$header_name]))
2304                                                                         $GLOBALS['_xh']['headers'][$header_name] .= ', ' . trim($cookie);
2305                                                                 else
2306                                                                         $GLOBALS['_xh']['headers'][$header_name] = trim($cookie);
2307                                                                 // parse cookie attributes, in case user wants to correctly honour them
2308                                                                 // feature creep: only allow rfc-compliant cookie attributes?
2309                                                                 $cookie = explode(';', $cookie);
2310                                                                 foreach ($cookie as $pos => $val)
2311                                                                 {
2312                                                                         $val = explode('=', $val, 2);
2313                                                                         $tag = trim($val[0]);
2314                                                                         $val = trim(@$val[1]);
2315                                                                         /// @todo with version 1 cookies, we should strip leading and trailing " chars
2316                                                                         if ($pos == 0)
2317                                                                         {
2318                                                                                 $cookiename = $tag;
2319                                                                                 $GLOBALS['_xh']['cookies'][$tag] = array();
2320                                                                                 $GLOBALS['_xh']['cookies'][$cookiename]['value'] = urldecode($val);
2321                                                                         }
2322                                                                         else
2323                                                                         {
2324                                                                                 $GLOBALS['_xh']['cookies'][$cookiename][$tag] = $val;
2325                                                                         }
2326                                                                 }
2327                                                         }
2328                                                 }
2329                                                 else
2330                                                 {
2331                                                         $GLOBALS['_xh']['headers'][$header_name] = trim($arr[1]);
2332                                                 }
2333                                         }
2334                                         elseif(isset($header_name))
2335                                         {
2336                                                 ///     @todo version1 cookies might span multiple lines, thus breaking the parsing above
2337                                                 $GLOBALS['_xh']['headers'][$header_name] .= ' ' . trim($line);
2338                                         }
2339                                 }
2340
2341                                 $data = substr($data, $bd);
2342
2343                                 if($this->debug && count($GLOBALS['_xh']['headers']))
2344                                 {
2345                                         print '<PRE>';
2346                                         foreach($GLOBALS['_xh']['headers'] as $header => $value)
2347                                         {
2348                                                 print htmlentities("HEADER: $header: $value\n");
2349                                         }
2350                                         foreach($GLOBALS['_xh']['cookies'] as $header => $value)
2351                                         {
2352                                                 print htmlentities("COOKIE: $header={$value['value']}\n");
2353                                         }
2354                                         print "</PRE>\n";
2355                                 }
2356
2357                                 // if CURL was used for the call, http headers have been processed,
2358                                 // and dechunking + reinflating have been carried out
2359                                 if(!$headers_processed)
2360                                 {
2361                                         // Decode chunked encoding sent by http 1.1 servers
2362                                         if(isset($GLOBALS['_xh']['headers']['transfer-encoding']) && $GLOBALS['_xh']['headers']['transfer-encoding'] == 'chunked')
2363                                         {
2364                                                 if(!$data = decode_chunked($data))
2365                                                 {
2366                                                         error_log('XML-RPC: xmlrpcmsg::parseResponse: errors occurred when trying to rebuild the chunked data received from server');
2367                                                         $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['dechunk_fail'], $GLOBALS['xmlrpcstr']['dechunk_fail']);
2368                                                         return $r;
2369                                                 }
2370                                         }
2371
2372                                         // Decode gzip-compressed stuff
2373                                         // code shamelessly inspired from nusoap library by Dietrich Ayala
2374                                         if(isset($GLOBALS['_xh']['headers']['content-encoding']))
2375                                         {
2376                                                 $GLOBALS['_xh']['headers']['content-encoding'] = str_replace('x-', '', $GLOBALS['_xh']['headers']['content-encoding']);
2377                                                 if($GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' || $GLOBALS['_xh']['headers']['content-encoding'] == 'gzip')
2378                                                 {
2379                                                         // if decoding works, use it. else assume data wasn't gzencoded
2380                                                         if(function_exists('gzinflate'))
2381                                                         {
2382                                                                 if($GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' && $degzdata = @gzuncompress($data))
2383                                                                 {
2384                                                                         $data = $degzdata;
2385                                                                         if($this->debug)
2386                                                                         print "<PRE>---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---</PRE>";
2387                                                                 }
2388                                                                 elseif($GLOBALS['_xh']['headers']['content-encoding'] == 'gzip' && $degzdata = @gzinflate(substr($data, 10)))
2389                                                                 {
2390                                                                         $data = $degzdata;
2391                                                                         if($this->debug)
2392                                                                         print "<PRE>---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---</PRE>";
2393                                                                 }
2394                                                                 else
2395                                                                 {
2396                                                                         error_log('XML-RPC: xmlrpcmsg::parseResponse: errors occurred when trying to decode the deflated data received from server');
2397                                                                         $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['decompress_fail'], $GLOBALS['xmlrpcstr']['decompress_fail']);
2398                                                                         return $r;
2399                                                                 }
2400                                                         }
2401                                                         else
2402                                                         {
2403                                                                 error_log('XML-RPC: xmlrpcmsg::parseResponse: the server sent deflated data. Your php install must have the Zlib extension compiled in to support this.');
2404                                                                 $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['cannot_decompress'], $GLOBALS['xmlrpcstr']['cannot_decompress']);
2405                                                                 return $r;
2406                                                         }
2407                                                 }
2408                                         }
2409                                 } // end of 'if needed, de-chunk, re-inflate response'
2410
2411                                 // real stupid hack to avoid PHP 4 complaining about returning NULL by ref
2412                                 $r = null;
2413                                 $r =& $r;
2414                                 return $r;
2415                 }
2416
2417                 /**
2418                 * Parse the xmlrpc response contained in the string $data and return an xmlrpcresp object.
2419                 * @param string $data the xmlrpc response, eventually including http headers
2420                 * @param bool $headers_processed when true prevents parsing HTTP headers for interpretation of content-encoding and consequent decoding
2421                 * @param string $return_type decides return type, i.e. content of response->value(). Either 'xmlrpcvals', 'xml' or 'phpvals'
2422                 * @return xmlrpcresp
2423                 * @access public
2424                 */
2425                 function &parseResponse($data='', $headers_processed=false, $return_type='xmlrpcvals')
2426                 {
2427                         if($this->debug)
2428                         {
2429                                 //by maHo, replaced htmlspecialchars with htmlentities
2430                                 print "<PRE>---GOT---\n" . htmlentities($data) . "\n---END---\n</PRE>";
2431                         }
2432
2433                         if($data == '')
2434                         {
2435                                 error_log('XML-RPC: xmlrpcmsg::parseResponse: no response received from server.');
2436                                 $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_data'], $GLOBALS['xmlrpcstr']['no_data']);
2437                                 return $r;
2438                         }
2439
2440                         $GLOBALS['_xh']=array();
2441
2442                         $raw_data = $data;
2443                         // parse the HTTP headers of the response, if present, and separate them from data
2444                         if(substr($data, 0, 4) == 'HTTP')
2445                         {
2446                                 $r =& $this->parseResponseHeaders($data, $headers_processed);
2447                                 if ($r)
2448                                 {
2449                                         // failed processing of HTTP response headers
2450                                         // save into response obj the full payload received, for debugging
2451                                         $r->raw_data = $data;
2452                                         return $r;
2453                                 }
2454                         }
2455                         else
2456                         {
2457                                 $GLOBALS['_xh']['headers'] = array();
2458                                 $GLOBALS['_xh']['cookies'] = array();
2459                         }
2460
2461                         if($this->debug)
2462                         {
2463                                 $start = strpos($data, '<!-- SERVER DEBUG INFO (BASE64 ENCODED):');
2464                                 if ($start)
2465                                 {
2466                                         $start += strlen('<!-- SERVER DEBUG INFO (BASE64 ENCODED):');
2467                                         $end = strpos($data, '-->', $start);
2468                                         $comments = substr($data, $start, $end-$start);
2469                                         print "<PRE>---SERVER DEBUG INFO (DECODED) ---\n\t".htmlentities(str_replace("\n", "\n\t", base64_decode($comments)))."\n---END---\n</PRE>";
2470                                 }
2471                         }
2472
2473                         // be tolerant of extra whitespace in response body
2474                         $data = trim($data);
2475
2476                         /// @todo return an error msg if $data=='' ?
2477
2478                         // be tolerant of junk after methodResponse (e.g. javascript ads automatically inserted by free hosts)
2479                         // idea from Luca Mariano <luca.mariano@email.it> originally in PEARified version of the lib
2480                         $bd = false;
2481                         // Poor man's version of strrpos for php 4...
2482                         $pos = strpos($data, '</methodResponse>');
2483                         while($pos || is_int($pos))
2484                         {
2485                                 $bd = $pos+17;
2486                                 $pos = strpos($data, '</methodResponse>', $bd);
2487                         }
2488                         if($bd)
2489                         {
2490                                 $data = substr($data, 0, $bd);
2491                         }
2492
2493                         // if user wants back raw xml, give it to him
2494                         if ($return_type == 'xml')
2495                         {
2496                                 $r =& new xmlrpcresp($data, 0, '', 'xml');
2497                                 $r->hdrs = $GLOBALS['_xh']['headers'];
2498                                 $r->_cookies = $GLOBALS['_xh']['cookies'];
2499                                 $r->raw_data = $raw_data;
2500                                 return $r;
2501                         }
2502
2503                         // try to 'guestimate' the character encoding of the received response
2504                         $resp_encoding = guess_encoding(@$GLOBALS['_xh']['headers']['content-type'], $data);
2505
2506                         $GLOBALS['_xh']['ac']='';
2507                         //$GLOBALS['_xh']['qt']=''; //unused...
2508                         $GLOBALS['_xh']['stack'] = array();
2509                         $GLOBALS['_xh']['valuestack'] = array();
2510                         $GLOBALS['_xh']['isf']=0; // 0 = OK, 1 for xmlrpc fault responses, 2 = invalid xmlrpc
2511                         $GLOBALS['_xh']['isf_reason']='';
2512                         $GLOBALS['_xh']['rt']=''; // 'methodcall or 'methodresponse'
2513
2514                         // if response charset encoding is not known / supported, try to use
2515                         // the default encoding and parse the xml anyway, but log a warning...
2516                         if (!in_array($resp_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
2517                         // the following code might be better for mb_string enabled installs, but
2518                         // makes the lib about 200% slower...
2519                         //if (!is_valid_charset($resp_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
2520                         {
2521                                 error_log('XML-RPC: xmlrpcmsg::parseResponse: invalid charset encoding of received response: '.$resp_encoding);
2522                                 $resp_encoding = $GLOBALS['xmlrpc_defencoding'];
2523                         }
2524                         $parser = xml_parser_create($resp_encoding);
2525                         xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
2526                         // G. Giunta 2005/02/13: PHP internally uses ISO-8859-1, so we have to tell
2527                         // the xml parser to give us back data in the expected charset
2528                         xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $GLOBALS['xmlrpc_internalencoding']);
2529
2530                         if ($return_type == 'phpvals')
2531                         {
2532                                 xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast');
2533                         }
2534                         else
2535                         {
2536                                 xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee');
2537                         }
2538
2539                         xml_set_character_data_handler($parser, 'xmlrpc_cd');
2540                         xml_set_default_handler($parser, 'xmlrpc_dh');
2541
2542                         // first error check: xml not well formed
2543                         if(!xml_parse($parser, $data, count($data)))
2544                         {
2545                                 // thanks to Peter Kocks <peter.kocks@baygate.com>
2546                                 if((xml_get_current_line_number($parser)) == 1)
2547                                 {
2548                                         $errstr = 'XML error at line 1, check URL';
2549                                 }
2550                                 else
2551                                 {
2552                                         $errstr = sprintf('XML error: %s at line %d, column %d',
2553                                                 xml_error_string(xml_get_error_code($parser)),
2554                                                 xml_get_current_line_number($parser), xml_get_current_column_number($parser));
2555                                 }
2556                                 error_log($errstr);
2557