OSDN Git Service

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