OSDN Git Service

定数名修正(nucleus/language/english.php)
[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                                 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'], $GLOBALS['xmlrpcstr']['invalid_return'].' ('.$errstr.')');
2558                                 xml_parser_free($parser);
2559                                 if($this->debug)
2560                                 {
2561                                         print $errstr;
2562                                 }
2563                                 $r->hdrs = $GLOBALS['_xh']['headers'];
2564                                 $r->_cookies = $GLOBALS['_xh']['cookies'];
2565                                 $r->raw_data = $raw_data;
2566                                 return $r;
2567                         }
2568                         xml_parser_free($parser);
2569                         // second error check: xml well formed but not xml-rpc compliant
2570                         if ($GLOBALS['_xh']['isf'] > 1)
2571                         {
2572                                 if ($this->debug)
2573                                 {
2574                                         /// @todo echo something for user?
2575                                 }
2576
2577                                 $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'],
2578                                 $GLOBALS['xmlrpcstr']['invalid_return'] . ' ' . $GLOBALS['_xh']['isf_reason']);
2579                         }
2580                         // third error check: parsing of the response has somehow gone boink.
2581                         // NB: shall we omit this check, since we trust the parsing code?
2582                         elseif ($return_type == 'xmlrpcvals' && !is_object($GLOBALS['_xh']['value']))
2583                         {
2584                                 // something odd has happened
2585                                 // and it's time to generate a client side error
2586                                 // indicating something odd went on
2587                                 $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'],
2588                                         $GLOBALS['xmlrpcstr']['invalid_return']);
2589                         }
2590                         else
2591                         {
2592                                 if ($this->debug)
2593                                 {
2594                                         print "<PRE>---PARSED---\n";
2595                                         // somehow htmlentities chokes on var_export, and some full html string...
2596                                         //print htmlentitites(var_export($GLOBALS['_xh']['value'], true));
2597                                         print htmlspecialchars(var_export($GLOBALS['_xh']['value'], true));
2598                                         print "\n---END---</PRE>";
2599                                 }
2600
2601                                 // note that using =& will raise an error if $GLOBALS['_xh']['st'] does not generate an object.
2602                                 $v =& $GLOBALS['_xh']['value'];
2603
2604                                 if($GLOBALS['_xh']['isf'])
2605                                 {
2606                                         /// @todo we should test here if server sent an int and a string,
2607                                         /// and/or coerce them into such...
2608                                         if ($return_type == 'xmlrpcvals')
2609                                         {
2610                                                 $errno_v = $v->structmem('faultCode');
2611                                                 $errstr_v = $v->structmem('faultString');
2612                                                 $errno = $errno_v->scalarval();
2613                                                 $errstr = $errstr_v->scalarval();
2614                                         }
2615                                         else
2616                                         {
2617                                                 $errno = $v['faultCode'];
2618                                                 $errstr = $v['faultString'];
2619                                         }
2620
2621                                         if($errno == 0)
2622                                         {
2623                                                 // FAULT returned, errno needs to reflect that
2624                                                 $errno = -1;
2625                                         }
2626
2627                                         $r =& new xmlrpcresp(0, $errno, $errstr);
2628                                 }
2629                                 else
2630                                 {
2631                                         $r=&new xmlrpcresp($v, 0, '', $return_type);
2632                                 }
2633                         }
2634
2635                         $r->hdrs = $GLOBALS['_xh']['headers'];
2636                         $r->_cookies = $GLOBALS['_xh']['cookies'];
2637                         $r->raw_data = $raw_data;
2638                         return $r;
2639                 }
2640         }
2641
2642         class xmlrpcval
2643         {
2644                 var $me=array();
2645                 var $mytype=0;
2646                 var $_php_class=null;
2647
2648                 /**
2649                 * @param mixed $val
2650                 * @param string $type any valid xmlrpc type name (lowercase). If null, 'string' is assumed
2651                 */
2652                 function xmlrpcval($val=-1, $type='')
2653                 {
2654                         /// @todo: optimization creep - do not call addXX, do it all inline.
2655                         /// downside: booleans will not be coerced anymore
2656                         if($val!==-1 || $type!='')
2657                         {
2658                                 // optimization creep: inlined all work done by constructor
2659                                 switch($type)
2660                                 {
2661                                         case '':
2662                                                 $this->mytype=1;
2663                                                 $this->me['string']=$val;
2664                                                 break;
2665                                         case 'i4':
2666                                         case 'int':
2667                                         case 'double':
2668                                         case 'string':
2669                                         case 'boolean':
2670                                         case 'dateTime.iso8601':
2671                                         case 'base64':
2672                                         case 'null':
2673                                                 $this->mytype=1;
2674                                                 $this->me[$type]=$val;
2675                                                 break;
2676                                         case 'array':
2677                                                 $this->mytype=2;
2678                                                 $this->me['array']=$val;
2679                                                 break;
2680                                         case 'struct':
2681                                                 $this->mytype=3;
2682                                                 $this->me['struct']=$val;
2683                                                 break;
2684                                         default:
2685                                                 error_log("XML-RPC: xmlrpcval::xmlrpcval: not a known type ($type)");
2686                                 }
2687                                 /*if($type=='')
2688                                 {
2689                                         $type='string';
2690                                 }
2691                                 if($GLOBALS['xmlrpcTypes'][$type]==1)
2692                                 {
2693                                         $this->addScalar($val,$type);
2694                                 }
2695                                 elseif($GLOBALS['xmlrpcTypes'][$type]==2)
2696                                 {
2697                                         $this->addArray($val);
2698                                 }
2699                                 elseif($GLOBALS['xmlrpcTypes'][$type]==3)
2700                                 {
2701                                         $this->addStruct($val);
2702                                 }*/
2703                         }
2704                 }
2705
2706                 /**
2707                 * Add a single php value to an (unitialized) xmlrpcval
2708                 * @param mixed $val
2709                 * @param string $type
2710                 * @return int 1 or 0 on failure
2711                 */
2712                 function addScalar($val, $type='string')
2713                 {
2714                         $typeof=@$GLOBALS['xmlrpcTypes'][$type];
2715                         if($typeof!=1)
2716                         {
2717                                 error_log("XML-RPC: xmlrpcval::addScalar: not a scalar type ($type)");
2718                                 return 0;
2719                         }
2720
2721                         // coerce booleans into correct values
2722                         // NB: we should iether do it for datetimes, integers and doubles, too,
2723                         // or just plain remove this check, implemnted on booleans only...
2724                         if($type==$GLOBALS['xmlrpcBoolean'])
2725                         {
2726                                 if(strcasecmp($val,'true')==0 || $val==1 || ($val==true && strcasecmp($val,'false')))
2727                                 {
2728                                         $val=true;
2729                                 }
2730                                 else
2731                                 {
2732                                         $val=false;
2733                                 }
2734                         }
2735
2736                         switch($this->mytype)
2737                         {
2738                                 case 1:
2739                                         error_log('XML-RPC: xmlrpcval::addScalar: scalar xmlrpcval can have only one value');
2740                                         return 0;
2741                                 case 3:
2742                                         error_log('XML-RPC: xmlrpcval::addScalar: cannot add anonymous scalar to struct xmlrpcval');
2743                                         return 0;
2744                                 case 2:
2745                                         // we're adding a scalar value to an array here
2746                                         //$ar=$this->me['array'];
2747                                         //$ar[]=&new xmlrpcval($val, $type);
2748                                         //$this->me['array']=$ar;
2749                                         // Faster (?) avoid all the costly array-copy-by-val done here...
2750                                         $this->me['array'][]=&new xmlrpcval($val, $type);
2751                                         return 1;
2752                                 default:
2753                                         // a scalar, so set the value and remember we're scalar
2754                                         $this->me[$type]=$val;
2755                                         $this->mytype=$typeof;
2756                                         return 1;
2757                         }
2758                 }
2759
2760                 /**
2761                 * Add an array of xmlrpcval objects to an xmlrpcval
2762                 * @param array $vals
2763                 * @return int 1 or 0 on failure
2764                 * @access public
2765                 *
2766                 * @todo add some checking for $vals to be an array of xmlrpcvals?
2767                 */
2768                 function addArray($vals)
2769                 {
2770                         if($this->mytype==0)
2771                         {
2772                                 $this->mytype=$GLOBALS['xmlrpcTypes']['array'];
2773                                 $this->me['array']=$vals;
2774                                 return 1;
2775                         }
2776                         elseif($this->mytype==2)
2777                         {
2778                                 // we're adding to an array here
2779                                 $this->me['array'] = array_merge($this->me['array'], $vals);
2780                                 return 1;
2781                         }
2782                         else
2783                         {
2784                                 error_log('XML-RPC: xmlrpcval::addArray: already initialized as a [' . $this->kindOf() . ']');
2785                                 return 0;
2786                         }
2787                 }
2788
2789                 /**
2790                 * Add an array of named xmlrpcval objects to an xmlrpcval
2791                 * @param array $vals
2792                 * @return int 1 or 0 on failure
2793                 * @access public
2794                 *
2795                 * @todo add some checking for $vals to be an array?
2796                 */
2797                 function addStruct($vals)
2798                 {
2799                         if($this->mytype==0)
2800                         {
2801                                 $this->mytype=$GLOBALS['xmlrpcTypes']['struct'];
2802                                 $this->me['struct']=$vals;
2803                                 return 1;
2804                         }
2805                         elseif($this->mytype==3)
2806                         {
2807                                 // we're adding to a struct here
2808                                 $this->me['struct'] = array_merge($this->me['struct'], $vals);
2809                                 return 1;
2810                         }
2811                         else
2812                         {
2813                                 error_log('XML-RPC: xmlrpcval::addStruct: already initialized as a [' . $this->kindOf() . ']');
2814                                 return 0;
2815                         }
2816                 }
2817
2818                 // poor man's version of print_r ???
2819                 // DEPRECATED!
2820                 function dump($ar)
2821                 {
2822                         foreach($ar as $key => $val)
2823                         {
2824                                 echo "$key => $val<br />";
2825                                 if($key == 'array')
2826                                 {
2827                                         while(list($key2, $val2) = each($val))
2828                                         {
2829                                                 echo "-- $key2 => $val2<br />";
2830                                         }
2831                                 }
2832                         }
2833                 }
2834
2835                 /**
2836                 * Returns a string containing "struct", "array" or "scalar" describing the base type of the value
2837                 * @return string
2838                 * @access public
2839                 */
2840                 function kindOf()
2841                 {
2842                         switch($this->mytype)
2843                         {
2844                                 case 3:
2845                                         return 'struct';
2846                                         break;
2847                                 case 2:
2848                                         return 'array';
2849                                         break;
2850                                 case 1:
2851                                         return 'scalar';
2852                                         break;
2853                                 default:
2854                                         return 'undef';
2855                         }
2856                 }
2857
2858                 /**
2859                 * @access private
2860                 */
2861                 function serializedata($typ, $val, $charset_encoding='')
2862                 {
2863                         $rs='';
2864                         switch(@$GLOBALS['xmlrpcTypes'][$typ])
2865                         {
2866                                 case 1:
2867                                         switch($typ)
2868                                         {
2869                                                 case $GLOBALS['xmlrpcBase64']:
2870                                                         $rs.="<${typ}>" . base64_encode($val) . "</${typ}>";
2871                                                         break;
2872                                                 case $GLOBALS['xmlrpcBoolean']:
2873                                                         $rs.="<${typ}>" . ($val ? '1' : '0') . "</${typ}>";
2874                                                         break;
2875                                                 case $GLOBALS['xmlrpcString']:
2876                                                         // G. Giunta 2005/2/13: do NOT use htmlentities, since
2877                                                         // it will produce named html entities, which are invalid xml
2878                                                         $rs.="<${typ}>" . xmlrpc_encode_entitites($val, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding). "</${typ}>";
2879                                                         break;
2880                                                 case $GLOBALS['xmlrpcInt']:
2881                                                 case $GLOBALS['xmlrpcI4']:
2882                                                         $rs.="<${typ}>".(int)$val."</${typ}>";
2883                                                         break;
2884                                                 case $GLOBALS['xmlrpcDouble']:
2885                                                         $rs.="<${typ}>".(double)$val."</${typ}>";
2886                                                         break;
2887                                                 case $GLOBALS['xmlrpcNull']:
2888                                                         $rs.="<nil/>";
2889                                                         break;
2890                                                 default:
2891                                                         // no standard type value should arrive here, but provide a possibility
2892                                                         // for xmlrpcvals of unknown type...
2893                                                         $rs.="<${typ}>${val}</${typ}>";
2894                                         }
2895                                         break;
2896                                 case 3:
2897                                         // struct
2898                                         if ($this->_php_class)
2899                                         {
2900                                                 $rs.='<struct php_class="' . $this->_php_class . "\">\n";
2901                                         }
2902                                         else
2903                                         {
2904                                                 $rs.="<struct>\n";
2905                                         }
2906                                         foreach($val as $key2 => $val2)
2907                                         {
2908                                                 $rs.='<member><name>'.xmlrpc_encode_entitites($key2, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding)."</name>\n";
2909                                                 //$rs.=$this->serializeval($val2);
2910                                                 $rs.=$val2->serialize($charset_encoding);
2911                                                 $rs.="</member>\n";
2912                                         }
2913                                         $rs.='</struct>';
2914                                         break;
2915                                 case 2:
2916                                         // array
2917                                         $rs.="<array>\n<data>\n";
2918                                         for($i=0; $i<count($val); $i++)
2919                                         {
2920                                                 //$rs.=$this->serializeval($val[$i]);
2921                                                 $rs.=$val[$i]->serialize($charset_encoding);
2922                                         }
2923                                         $rs.="</data>\n</array>";
2924                                         break;
2925                                 default:
2926                                         break;
2927                         }
2928                         return $rs;
2929                 }
2930
2931                 /**
2932                 * Returns xml representation of the value. XML prologue not included
2933                 * @param string $charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed
2934                 * @return string
2935                 * @access public
2936                 */
2937                 function serialize($charset_encoding='')
2938                 {
2939                         // add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals...
2940                         //if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval')))
2941                         //{
2942                                 reset($this->me);
2943                                 list($typ, $val) = each($this->me);
2944                                 return '<value>' . $this->serializedata($typ, $val, $charset_encoding) . "</value>\n";
2945                         //}
2946                 }
2947
2948                 // DEPRECATED
2949                 function serializeval($o)
2950                 {
2951                         // add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals...
2952                         //if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval')))
2953                         //{
2954                                 $ar=$o->me;
2955                                 reset($ar);
2956                                 list($typ, $val) = each($ar);
2957                                 return '<value>' . $this->serializedata($typ, $val) . "</value>\n";
2958                         //}
2959                 }
2960
2961                 /**
2962                 * Checks wheter a struct member with a given name is present.
2963                 * Works only on xmlrpcvals of type struct.
2964                 * @param string $m the name of the struct member to be looked up
2965                 * @return boolean
2966                 * @access public
2967                 */
2968                 function structmemexists($m)
2969                 {
2970                         return array_key_exists($m, $this->me['struct']);
2971                 }
2972
2973                 /**
2974                 * Returns the value of a given struct member (an xmlrpcval object in itself).
2975                 * Will raise a php warning if struct member of given name does not exist
2976                 * @param string $m the name of the struct member to be looked up
2977                 * @return xmlrpcval
2978                 * @access public
2979                 */
2980                 function structmem($m)
2981                 {
2982                         return $this->me['struct'][$m];
2983                 }
2984
2985                 /**
2986                 * Reset internal pointer for xmlrpcvals of type struct.
2987                 * @access public
2988                 */
2989                 function structreset()
2990                 {
2991                         reset($this->me['struct']);
2992                 }
2993
2994                 /**
2995                 * Return next member element for xmlrpcvals of type struct.
2996                 * @return xmlrpcval
2997                 * @access public
2998                 */
2999                 function structeach()
3000                 {
3001                         return each($this->me['struct']);
3002                 }
3003
3004                 // DEPRECATED! this code looks like it is very fragile and has not been fixed
3005                 // for a long long time. Shall we remove it for 2.0?
3006                 function getval()
3007                 {
3008                         // UNSTABLE
3009                         reset($this->me);
3010                         list($a,$b)=each($this->me);
3011                         // contributed by I Sofer, 2001-03-24
3012                         // add support for nested arrays to scalarval
3013                         // i've created a new method here, so as to
3014                         // preserve back compatibility
3015
3016                         if(is_array($b))
3017                         {
3018                                 @reset($b);
3019                                 while(list($id,$cont) = @each($b))
3020                                 {
3021                                         $b[$id] = $cont->scalarval();
3022                                 }
3023                         }
3024
3025                         // add support for structures directly encoding php objects
3026                         if(is_object($b))
3027                         {
3028                                 $t = get_object_vars($b);
3029                                 @reset($t);
3030                                 while(list($id,$cont) = @each($t))
3031                                 {
3032                                         $t[$id] = $cont->scalarval();
3033                                 }
3034                                 @reset($t);
3035                                 while(list($id,$cont) = @each($t))
3036                                 {
3037                                         @$b->$id = $cont;
3038                                 }
3039                         }
3040                         // end contrib
3041                         return $b;
3042                 }
3043
3044                 /**
3045                 * Returns the value of a scalar xmlrpcval
3046                 * @return mixed
3047                 * @access public
3048                 */
3049                 function scalarval()
3050                 {
3051                         reset($this->me);
3052                         list(,$b)=each($this->me);
3053                         return $b;
3054                 }
3055
3056                 /**
3057                 * Returns the type of the xmlrpcval.
3058                 * For integers, 'int' is always returned in place of 'i4'
3059                 * @return string
3060                 * @access public
3061                 */
3062                 function scalartyp()
3063                 {
3064                         reset($this->me);
3065                         list($a,)=each($this->me);
3066                         if($a==$GLOBALS['xmlrpcI4'])
3067                         {
3068                                 $a=$GLOBALS['xmlrpcInt'];
3069                         }
3070                         return $a;
3071                 }
3072
3073                 /**
3074                 * Returns the m-th member of an xmlrpcval of struct type
3075                 * @param integer $m the index of the value to be retrieved (zero based)
3076                 * @return xmlrpcval
3077                 * @access public
3078                 */
3079                 function arraymem($m)
3080                 {
3081                         return $this->me['array'][$m];
3082                 }
3083
3084                 /**
3085                 * Returns the number of members in an xmlrpcval of array type
3086                 * @return integer
3087                 * @access public
3088                 */
3089                 function arraysize()
3090                 {
3091                         return count($this->me['array']);
3092                 }
3093
3094                 /**
3095                 * Returns the number of members in an xmlrpcval of struct type
3096                 * @return integer
3097                 * @access public
3098                 */
3099                 function structsize()
3100                 {
3101                         return count($this->me['struct']);
3102                 }
3103         }
3104
3105
3106         // date helpers
3107
3108         /**
3109         * Given a timestamp, return the corresponding ISO8601 encoded string.
3110         *
3111         * Really, timezones ought to be supported
3112         * but the XML-RPC spec says:
3113         *
3114         * "Don't assume a timezone. It should be specified by the server in its
3115         * documentation what assumptions it makes about timezones."
3116         *
3117         * These routines always assume localtime unless
3118         * $utc is set to 1, in which case UTC is assumed
3119         * and an adjustment for locale is made when encoding
3120         *
3121         * @param int $timet (timestamp)
3122         * @param int $utc (0 or 1)
3123         * @return string
3124         */
3125         function iso8601_encode($timet, $utc=0)
3126         {
3127                 if(!$utc)
3128                 {
3129                         $t=strftime("%Y%m%dT%H:%M:%S", $timet);
3130                 }
3131                 else
3132                 {
3133                         if(function_exists('gmstrftime'))
3134                         {
3135                                 // gmstrftime doesn't exist in some versions
3136                                 // of PHP
3137                                 $t=gmstrftime("%Y%m%dT%H:%M:%S", $timet);
3138                         }
3139                         else
3140                         {
3141                                 $t=strftime("%Y%m%dT%H:%M:%S", $timet-date('Z'));
3142                         }
3143                 }
3144                 return $t;
3145         }
3146
3147         /**
3148         * Given an ISO8601 date string, return a timet in the localtime, or UTC
3149         * @param string $idate
3150         * @param int $utc either 0 or 1
3151         * @return int (datetime)
3152         */
3153         function iso8601_decode($idate, $utc=0)
3154         {
3155                 $t=0;
3156                 if(preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})/', $idate, $regs))
3157                 {
3158                         if($utc)
3159                         {
3160                                 $t=gmmktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
3161                         }
3162                         else
3163                         {
3164                                 $t=mktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
3165                         }
3166                 }
3167                 return $t;
3168         }
3169
3170         /**
3171         * Takes an xmlrpc value in PHP xmlrpcval object format and translates it into native PHP types.
3172         *
3173         * Works with xmlrpc message objects as input, too.
3174         *
3175         * Given proper options parameter, can rebuild generic php object instances
3176         * (provided those have been encoded to xmlrpc format using a corresponding
3177         * option in php_xmlrpc_encode())
3178         * PLEASE NOTE that rebuilding php objects involves calling their constructor function.
3179         * This means that the remote communication end can decide which php code will
3180         * get executed on your server, leaving the door possibly open to 'php-injection'
3181         * style of attacks (provided you have some classes defined on your server that
3182         * might wreak havoc if instances are built outside an appropriate context).
3183         * Make sure you trust the remote server/client before eanbling this!
3184         *
3185         * @author Dan Libby (dan@libby.com)
3186         *
3187         * @param xmlrpcval $xmlrpc_val
3188         * @param array $options if 'decode_php_objs' is set in the options array, xmlrpc structs can be decoded into php objects
3189         * @return mixed
3190         */
3191         function php_xmlrpc_decode($xmlrpc_val, $options=array())
3192         {
3193                 switch($xmlrpc_val->kindOf())
3194                 {
3195                         case 'scalar':
3196                                 if (in_array('extension_api', $options))
3197                                 {
3198                                         reset($xmlrpc_val->me);
3199                                         list($typ,$val) = each($xmlrpc_val->me);
3200                                         switch ($typ)
3201                                         {
3202                                                 case 'dateTime.iso8601':
3203                                                         $xmlrpc_val->scalar = $val;
3204                                                         $xmlrpc_val->xmlrpc_type = 'datetime';
3205                                                         $xmlrpc_val->timestamp = iso8601_decode($val);
3206                                                         return $xmlrpc_val;
3207                                                 case 'base64':
3208                                                         $xmlrpc_val->scalar = $val;
3209                                                         $xmlrpc_val->type = $typ;
3210                                                         return $xmlrpc_val;
3211                                                 default:
3212                                                         return $xmlrpc_val->scalarval();
3213                                         }
3214                                 }
3215                                 return $xmlrpc_val->scalarval();
3216                         case 'array':
3217                                 $size = $xmlrpc_val->arraysize();
3218                                 $arr = array();
3219                                 for($i = 0; $i < $size; $i++)
3220                                 {
3221                                         $arr[] = php_xmlrpc_decode($xmlrpc_val->arraymem($i), $options);
3222                                 }
3223                                 return $arr;
3224                         case 'struct':
3225                                 $xmlrpc_val->structreset();
3226                                 // If user said so, try to rebuild php objects for specific struct vals.
3227                                 /// @todo should we raise a warning for class not found?
3228                                 // shall we check for proper subclass of xmlrpcval instead of
3229                                 // presence of _php_class to detect what we can do?
3230                                 if (in_array('decode_php_objs', $options) && $xmlrpc_val->_php_class != ''
3231                                         && class_exists($xmlrpc_val->_php_class))
3232                                 {
3233                                         $obj = @new $xmlrpc_val->_php_class;
3234                                         while(list($key,$value)=$xmlrpc_val->structeach())
3235                                         {
3236                                                 $obj->$key = php_xmlrpc_decode($value, $options);
3237                                         }
3238                                         return $obj;
3239                                 }
3240                                 else
3241                                 {
3242                                         $arr = array();
3243                                         while(list($key,$value)=$xmlrpc_val->structeach())
3244                                         {
3245                                                 $arr[$key] = php_xmlrpc_decode($value, $options);
3246                                         }
3247                                         return $arr;
3248                                 }
3249                         case 'msg':
3250                                 $paramcount = $xmlrpc_val->getNumParams();
3251                                 $arr = array();
3252                                 for($i = 0; $i < $paramcount; $i++)
3253                                 {
3254                                         $arr[] = php_xmlrpc_decode($xmlrpc_val->getParam($i));
3255                                 }
3256                                 return $arr;
3257                         }
3258         }
3259
3260         // This constant left here only for historical reasons...
3261         // it was used to decide if we have to define xmlrpc_encode on our own, but
3262         // we do not do it anymore
3263         if(function_exists('xmlrpc_decode'))
3264         {
3265                 define('XMLRPC_EPI_ENABLED','1');
3266         }
3267         else
3268         {
3269                 define('XMLRPC_EPI_ENABLED','0');
3270         }
3271
3272         /**
3273         * Takes native php types and encodes them into xmlrpc PHP object format.
3274         * It will not re-encode xmlrpcval objects.
3275         *
3276         * Feature creep -- could support more types via optional type argument
3277         * (string => datetime support has been added, ??? => base64 not yet)
3278         *
3279         * If given a proper options parameter, php object instances will be encoded
3280         * into 'special' xmlrpc values, that can later be decoded into php objects
3281         * by calling php_xmlrpc_decode() with a corresponding option
3282         *
3283         * @author Dan Libby (dan@libby.com)
3284         *
3285         * @param mixed $php_val the value to be converted into an xmlrpcval object
3286         * @param array $options can include 'encode_php_objs', 'auto_dates', 'null_extension' or 'extension_api'
3287         * @return xmlrpcval
3288         */
3289         function &php_xmlrpc_encode($php_val, $options=array())
3290         {
3291                 $type = gettype($php_val);
3292                 switch($type)
3293                 {
3294                         case 'string':
3295                                 if (in_array('auto_dates', $options) && preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $php_val))
3296                                         $xmlrpc_val =& new xmlrpcval($php_val, $GLOBALS['xmlrpcDateTime']);
3297                                 else
3298                                         $xmlrpc_val =& new xmlrpcval($php_val, $GLOBALS['xmlrpcString']);
3299                                 break;
3300                         case 'integer':
3301                                 $xmlrpc_val =& new xmlrpcval($php_val, $GLOBALS['xmlrpcInt']);
3302                                 break;
3303                         case 'double':
3304                                 $xmlrpc_val =& new xmlrpcval($php_val, $GLOBALS['xmlrpcDouble']);
3305                                 break;
3306                                 // <G_Giunta_2001-02-29>
3307                                 // Add support for encoding/decoding of booleans, since they are supported in PHP
3308                         case 'boolean':
3309                                 $xmlrpc_val =& new xmlrpcval($php_val, $GLOBALS['xmlrpcBoolean']);
3310                                 break;
3311                                 // </G_Giunta_2001-02-29>
3312                         case 'array':
3313                                 // PHP arrays can be encoded to either xmlrpc structs or arrays,
3314                                 // depending on wheter they are hashes or plain 0..n integer indexed
3315                                 // A shorter one-liner would be
3316                                 // $tmp = array_diff(array_keys($php_val), range(0, count($php_val)-1));
3317                                 // but execution time skyrockets!
3318                                 $j = 0;
3319                                 $arr = array();
3320                                 $ko = false;
3321                                 foreach($php_val as $key => $val)
3322                                 {
3323                                         $arr[$key] =& php_xmlrpc_encode($val, $options);
3324                                         if(!$ko && $key !== $j)
3325                                         {
3326                                                 $ko = true;
3327                                         }
3328                                         $j++;
3329                                 }
3330                                 if($ko)
3331                                 {
3332                                         $xmlrpc_val =& new xmlrpcval($arr, $GLOBALS['xmlrpcStruct']);
3333                                 }
3334                                 else
3335                                 {
3336                                         $xmlrpc_val =& new xmlrpcval($arr, $GLOBALS['xmlrpcArray']);
3337                                 }
3338                                 break;
3339                         case 'object':
3340                                 if(is_a($php_val, 'xmlrpcval'))
3341                                 {
3342                                         $xmlrpc_val = $php_val;
3343                                 }
3344                                 else
3345                                 {
3346                                         $arr = array();
3347                                         while(list($k,$v) = each($php_val))
3348                                         {
3349                                                 $arr[$k] = php_xmlrpc_encode($v, $options);
3350                                         }
3351                                         $xmlrpc_val =& new xmlrpcval($arr, $GLOBALS['xmlrpcStruct']);
3352                                         if (in_array('encode_php_objs', $options))
3353                                         {
3354                                                 // let's save original class name into xmlrpcval:
3355                                                 // might be useful later on...
3356                                                 $xmlrpc_val->_php_class = get_class($php_val);
3357                                         }
3358                                 }
3359                                 break;
3360                         case 'NULL':
3361                                 if (in_array('extension_api', $options))
3362                                 {
3363                                         $xmlrpc_val =& new xmlrpcval('', $GLOBALS['xmlrpcString']);
3364                                 }
3365                                 if (in_array('null_extension', $options))
3366                                 {
3367                                         $xmlrpc_val =& new xmlrpcval('', $GLOBALS['xmlrpcNull']);
3368                                 }
3369                                 else
3370                                 {
3371                                         $xmlrpc_val =& new xmlrpcval();
3372                                 }
3373                                 break;
3374                         case 'resource':
3375                                 if (in_array('extension_api', $options))
3376                                 {
3377                                         $xmlrpc_val =& new xmlrpcval((int)$php_val, $GLOBALS['xmlrpcInt']);
3378                                 }
3379                                 else
3380                                 {
3381                                         $xmlrpc_val =& new xmlrpcval();
3382                                 }
3383                         // catch "user function", "unknown type"
3384                         default:
3385                                 // giancarlo pinerolo <ping@alt.it>
3386                                 // it has to return
3387                                 // an empty object in case, not a boolean.
3388                                 $xmlrpc_val =& new xmlrpcval();
3389                                 break;
3390                         }
3391                         return $xmlrpc_val;
3392         }
3393
3394         /**
3395         * Convert the xml representation of a method response, method request or single
3396         * xmlrpc value into the appropriate object (a.k.a. deserialize)
3397         * @param string $xml_val
3398         * @param array $options
3399         * @return mixed false on error, or an instance of either xmlrpcval, xmlrpcmsg or xmlrpcresp
3400         */
3401         function php_xmlrpc_decode_xml($xml_val, $options=array())
3402         {
3403                 $GLOBALS['_xh'] = array();
3404                 $GLOBALS['_xh']['ac'] = '';
3405                 $GLOBALS['_xh']['stack'] = array();
3406                 $GLOBALS['_xh']['valuestack'] = array();
3407                 $GLOBALS['_xh']['params'] = array();
3408                 $GLOBALS['_xh']['pt'] = array();
3409                 $GLOBALS['_xh']['isf'] = 0;
3410                 $GLOBALS['_xh']['isf_reason'] = '';
3411                 $GLOBALS['_xh']['method'] = false;
3412                 $GLOBALS['_xh']['rt'] = '';
3413                 /// @todo 'guestimate' encoding
3414                 $parser = xml_parser_create();
3415                 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
3416                 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $GLOBALS['xmlrpc_internalencoding']);
3417                 xml_set_element_handler($parser, 'xmlrpc_se_any', 'xmlrpc_ee');
3418                 xml_set_character_data_handler($parser, 'xmlrpc_cd');
3419                 xml_set_default_handler($parser, 'xmlrpc_dh');
3420                 if(!xml_parse($parser, $xml_val, 1))
3421                 {
3422                         $errstr = sprintf('XML error: %s at line %d, column %d',
3423                                                 xml_error_string(xml_get_error_code($parser)),
3424                                                 xml_get_current_line_number($parser), xml_get_current_column_number($parser));
3425                         error_log($errstr);
3426                         xml_parser_free($parser);
3427                         return false;
3428                 }
3429                 xml_parser_free($parser);
3430                 if ($GLOBALS['_xh']['isf'] > 1) // test that $GLOBALS['_xh']['value'] is an obj, too???
3431                 {
3432                         error_log($GLOBALS['_xh']['isf_reason']);
3433                         return false;
3434                 }
3435                 switch ($GLOBALS['_xh']['rt'])
3436                 {
3437                         case 'methodresponse':
3438                                 $v =& $GLOBALS['_xh']['value'];
3439                                 if ($GLOBALS['_xh']['isf'] == 1)
3440                                 {
3441                                         $vc = $v->structmem('faultCode');
3442                                         $vs = $v->structmem('faultString');
3443                                         $r =& new xmlrpcresp(0, $vc->scalarval(), $vs->scalarval());
3444                                 }
3445                                 else
3446                                 {
3447                                         $r =& new xmlrpcresp($v);
3448                                 }
3449                                 return $r;
3450                         case 'methodcall':
3451                                 $m =& new xmlrpcmsg($GLOBALS['_xh']['method']);
3452                                 for($i=0; $i < count($GLOBALS['_xh']['params']); $i++)
3453                                 {
3454                                         $m->addParam($GLOBALS['_xh']['params'][$i]);
3455                                 }
3456                                 return $m;
3457                         case 'value':
3458                                 return $GLOBALS['_xh']['value'];
3459                         default:
3460                                 return false;
3461                 }
3462         }
3463
3464         /**
3465         * decode a string that is encoded w/ "chunked" transfer encoding
3466         * as defined in rfc2068 par. 19.4.6
3467         * code shamelessly stolen from nusoap library by Dietrich Ayala
3468         *
3469         * @param string $buffer the string to be decoded
3470         * @return string
3471         */
3472         function decode_chunked($buffer)
3473         {
3474                 // length := 0
3475                 $length = 0;
3476                 $new = '';
3477
3478                 // read chunk-size, chunk-extension (if any) and crlf
3479                 // get the position of the linebreak
3480                 $chunkend = strpos($buffer,"\r\n") + 2;
3481                 $temp = substr($buffer,0,$chunkend);
3482                 $chunk_size = hexdec( trim($temp) );
3483                 $chunkstart = $chunkend;
3484                 while($chunk_size > 0)
3485                 {
3486                         $chunkend = strpos($buffer, "\r\n", $chunkstart + $chunk_size);
3487
3488                         // just in case we got a broken connection
3489                         if($chunkend == false)
3490                         {
3491                                 $chunk = substr($buffer,$chunkstart);
3492                                 // append chunk-data to entity-body
3493                                 $new .= $chunk;
3494                                 $length += strlen($chunk);
3495                                 break;
3496                         }
3497
3498                         // read chunk-data and crlf
3499                         $chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart);
3500                         // append chunk-data to entity-body
3501                         $new .= $chunk;
3502                         // length := length + chunk-size
3503                         $length += strlen($chunk);
3504                         // read chunk-size and crlf
3505                         $chunkstart = $chunkend + 2;
3506
3507                         $chunkend = strpos($buffer,"\r\n",$chunkstart)+2;
3508                         if($chunkend == false)
3509                         {
3510                                 break; //just in case we got a broken connection
3511                         }
3512                         $temp = substr($buffer,$chunkstart,$chunkend-$chunkstart);
3513                         $chunk_size = hexdec( trim($temp) );
3514                         $chunkstart = $chunkend;
3515                 }
3516                 return $new;
3517         }
3518
3519         /**
3520         * xml charset encoding guessing helper function.
3521         * Tries to determine the charset encoding of an XML chunk
3522         * received over HTTP.
3523         * NB: according to the spec (RFC 3023, if text/xml content-type is received over HTTP without a content-type,
3524         * we SHOULD assume it is strictly US-ASCII. But we try to be more tolerant of unconforming (legacy?) clients/servers,
3525         * which will be most probably using UTF-8 anyway...
3526         *
3527         * @param string $httpheaders the http Content-type header
3528         * @param string $xmlchunk xml content buffer
3529         * @param string $encoding_prefs comma separated list of character encodings to be used as default (when mb extension is enabled)
3530         *
3531         * @todo explore usage of mb_http_input(): does it detect http headers + post data? if so, use it instead of hand-detection!!!
3532         */
3533         function guess_encoding($httpheader='', $xmlchunk='', $encoding_prefs=null)
3534         {
3535                 // discussion: see http://www.yale.edu/pclt/encoding/
3536                 // 1 - test if encoding is specified in HTTP HEADERS
3537
3538                 //Details:
3539                 // LWS:           (\13\10)?( |\t)+
3540                 // token:         (any char but excluded stuff)+
3541                 // header:        Content-type = ...; charset=value(; ...)*
3542                 //   where value is of type token, no LWS allowed between 'charset' and value
3543                 // Note: we do not check for invalid chars in VALUE:
3544                 //   this had better be done using pure ereg as below
3545
3546                 /// @todo this test will pass if ANY header has charset specification, not only Content-Type. Fix it?
3547                 $matches = array();
3548                 if(preg_match('/;\s*charset=([^;]+)/i', $httpheader, $matches))
3549                 {
3550                         return strtoupper(trim($matches[1]));
3551                 }
3552
3553                 // 2 - scan the first bytes of the data for a UTF-16 (or other) BOM pattern
3554                 //     (source: http://www.w3.org/TR/2000/REC-xml-20001006)
3555                 //     NOTE: actually, according to the spec, even if we find the BOM and determine
3556                 //     an encoding, we should check if there is an encoding specified
3557                 //     in the xml declaration, and verify if they match.
3558                 /// @todo implement check as described above?
3559                 /// @todo implement check for first bytes of string even without a BOM? (It sure looks harder than for cases WITH a BOM)
3560                 if(preg_match('/^(\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\x00\x00\xFF\xFE|\xFE\xFF\x00\x00)/', $xmlchunk))
3561                 {
3562                         return 'UCS-4';
3563                 }
3564                 elseif(preg_match('/^(\xFE\xFF|\xFF\xFE)/', $xmlchunk))
3565                 {
3566                         return 'UTF-16';
3567                 }
3568                 elseif(preg_match('/^(\xEF\xBB\xBF)/', $xmlchunk))
3569                 {
3570                         return 'UTF-8';
3571                 }
3572
3573                 // 3 - test if encoding is specified in the xml declaration
3574                 // Details:
3575                 // SPACE:         (#x20 | #x9 | #xD | #xA)+ === [ \x9\xD\xA]+
3576                 // EQ:            SPACE?=SPACE? === [ \x9\xD\xA]*=[ \x9\xD\xA]*
3577                 if (preg_match('/^<\?xml\s+version\s*=\s*'. "((?:\"[a-zA-Z0-9_.:-]+\")|(?:'[a-zA-Z0-9_.:-]+'))".
3578                         '\s+encoding\s*=\s*' . "((?:\"[A-Za-z][A-Za-z0-9._-]*\")|(?:'[A-Za-z][A-Za-z0-9._-]*'))/",
3579                         $xmlchunk, $matches))
3580                 {
3581                         return strtoupper(substr($matches[2], 1, -1));
3582                 }
3583
3584                 // 4 - if mbstring is available, let it do the guesswork
3585                 // NB: we favour finding an encoding that is compatible with what we can process
3586                 if(extension_loaded('mbstring'))
3587                 {
3588                         if($encoding_prefs)
3589                         {
3590                                 $enc = mb_detect_encoding($xmlchunk, $encoding_prefs);
3591                         }
3592                         else
3593                         {
3594                                 $enc = mb_detect_encoding($xmlchunk);
3595                         }
3596                         // NB: mb_detect likes to call it ascii, xml parser likes to call it US_ASCII...
3597                         // IANA also likes better US-ASCII, so go with it
3598                         if($enc == 'ASCII')
3599                         {
3600                                 $enc = 'US-'.$enc;
3601                         }
3602                         return $enc;
3603                 }
3604                 else
3605                 {
3606                         // no encoding specified: as per HTTP1.1 assume it is iso-8859-1?
3607                         // Both RFC 2616 (HTTP 1.1) and 1945(http 1.0) clearly state that for text/xxx content types
3608                         // this should be the standard. And we should be getting text/xml as request and response.
3609                         // BUT we have to be backward compatible with the lib, which always used UTF-8 as default...
3610                         return $GLOBALS['xmlrpc_defencoding'];
3611                 }
3612         }
3613
3614         /**
3615         * Checks if a given charset encoding is present in a list of encodings or
3616         * if it is a valid subset of any encoding in the list
3617         * @param string $encoding charset to be tested
3618         * @param mixed $validlist comma separated list of valid charsets (or array of charsets)
3619         */
3620         function is_valid_charset($encoding, $validlist)
3621         {
3622                 $charset_supersets = array(
3623                         'US-ASCII' => array ('ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4',
3624                                 'ISO-8859-5', 'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8',
3625                                 'ISO-8859-9', 'ISO-8859-10', 'ISO-8859-11', 'ISO-8859-12',
3626                                 'ISO-8859-13', 'ISO-8859-14', 'ISO-8859-15', 'UTF-8',
3627                                 'EUC-JP', 'EUC-', 'EUC-KR', 'EUC-CN')
3628                 );
3629                 if (is_string($validlist))
3630                         $validlist = explode(',', $validlist);
3631                 if (@in_array(strtoupper($encoding), $validlist))
3632                         return true;
3633                 else
3634                 {
3635                         if (array_key_exists($encoding, $charset_supersets))
3636                                 foreach ($validlist as $allowed)
3637                                         if (in_array($allowed, $charset_supersets[$encoding]))
3638                                                 return true;
3639                                 return false;
3640                 }
3641         }
3642
3643 ?>