[ Index ]

PHP Cross Reference of Joomla 1.5.26 DE

title

Body

[close]

/libraries/phpxmlrpc/ -> xmlrpc.php (source)

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


Generated: Wed Mar 28 15:54:07 2012 Cross-referenced by PHPXref 0.7.1