b2evolution PHP Cross Reference Blogging Systems

Source: /inc/_ext/xmlrpc/_xmlrpc.inc.php - 3785 lines - 114948 bytes - Summary - Text - Print

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

title

Description

title

Description

title

Description

title

title

Body