source: ether_eccad/trunk/ECCAD_INTERFACE/original/xmlrpc.inc @ 68

Last change on this file since 68 was 68, checked in by cbipsl, 14 years ago

commit v1 eccad

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