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