| [ Index ] |
PHP Cross Reference of Joomla 1.5.26 DE |
[Summary view] [Print] [Text view]
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'=>'€', '\x81'=>'?', '\x82'=>'‚', '\x83'=>'ƒ', 155 '\x84'=>'„', '\x85'=>'…', '\x86'=>'†', \x87'=>'‡', 156 '\x88'=>'ˆ', '\x89'=>'‰', '\x8A'=>'Š', '\x8B'=>'‹', 157 '\x8C'=>'Œ', '\x8D'=>'?', '\x8E'=>'Ž', '\x8F'=>'?', 158 '\x90'=>'?', '\x91'=>'‘', '\x92'=>'’', '\x93'=>'“', 159 '\x94'=>'”', '\x95'=>'•', '\x96'=>'–', '\x97'=>'—', 160 '\x98'=>'˜', '\x99'=>'™', '\x9A'=>'š', '\x9B'=>'›', 161 '\x9C'=>'œ', '\x9D'=>'?', '\x9E'=>'ž', '\x9F'=>'Ÿ' 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('&', '"', ''', '<', '>'), $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('&', '"', ''', '<', '>'), $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('&', '"', ''', '<', '>'), $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 .= '"'; 313 break; 314 case 38: 315 $escaped_data .= '&'; 316 break; 317 case 39: 318 $escaped_data .= '''; 319 break; 320 case 60: 321 $escaped_data .= '<'; 322 break; 323 case 62: 324 $escaped_data .= '>'; 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 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Wed Mar 28 15:54:07 2012 | Cross-referenced by PHPXref 0.7.1 |